参考:

https://fynch3r.github.io/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%A2%B3%E7%90%86/

https://xz.aliyun.com/t/11372

前言

本文没有漏洞分析,只有反序列化流程分析与各版本的防御手段总结。

其中关于防御手段,我找的文章没有对我胃口的,于是作了总结, 什么版本哪里防,怎么防,十分甚至九分清楚。

防御手段清楚了,才知道什么版本用什么payload。各种payload本文不讲。

反序列化流程

要点

1、执行反序列化的主体是TreeUnmarshaller。

2、正在转换的当前对象,若其中包含别的对象,则通过HierarchicalStreams#readClassType获取对象类型

3、获取到类型后,调用TreeUnmarshaller#convertAnother

4、convertAnother会进入到TreeUnmarshaller#convert

5、转换对象时,根据对象的类型,选择不同的converter,细节交给converter处理。

6、每个converter都有一个canConvert方法,判断当前type是否能convert。

7、converter#unmarshal,创建一个空对象,然后用converter#doUnmarshal转换该对象的属性。

8、若属性是个对象,则调用converter#unmarshalField,继续调用TreeUnmarshaller#convertAnother

TreeUnmarshaller#convert的实现:

protected Object convert(Object parent, Class type, Converter converter) {
try {
types.push(type);
Object result = converter.unmarshal(reader, this);
types.popSilently();
return result;
}catch(){}

环境

版本1.4.6

public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("xstream/payload.xml");
XStream xStream = new XStream(new DomDriver());
xStream.fromXML(fileInputStream);
}

payload.xml文件

<sorted-set>
<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>

main

public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("xstream/payload.xml");
XStream xStream = new XStream(new DomDriver());
xStream.fromXML(fileInputStream);
}

分析

要搞明白的问题:

1、哪里创建对象

2、哪里为对象的属性赋值

断点打在ProcessBuilder#start

image-20240308142538568.png

来到TreeMapConverter#populateTreeMap

image-20240308142844524.png

可以看到,sortedMap里有创建出来的对象,所以要从sortedMap生成的地方进入。

那就是上面的putCurrentEntryIntoMap,断点打在这里。

随后从这里一直深入,就可以看到创建各个对象的过程。

这里正在转换最外层的set集合。

image-20240308094234971.png

这里正在转换set集合里面的dynamic-proxy

image-20240308094255838.png

这里正在转换dynamic-proxy的interface

image-20240308094306436.png

这里在为dynamic-proxy的target属性赋值

image-20240308095840731.png

然后后面就是创建ProcessBuilder了,流程和上面几乎一样。

防御手段

1.4.6 < version < 1.4.10,靠ReflectionConverter#canConvert

1.4.10 没有防御。

1.4.11<= version <= 1.4.13 靠InternalBlackList#canConvert

version >= 1.4.14,靠内置黑名单。

用上面的payload打1.4.10可以打,不知为何。

image-20240308144946777.png

设置canConvert

用上面的payload打1.4.7会有这个错误

image-20240308132500777.png

修复思路:由于exp主要用到了java.beans.EventHandler,那么令他的converter不能convert即可。对应要点概要第6点。

ReflectionConverter

首先根据上面的报错栈,找到EventHandler的converter是哪个,然后看两个版本的converter差别在哪。

1.4.6

image-20240308132323554.png

1.4.7

image-20240308132259227.png

所以原本能convert EventHandler的converter现在不能converter了。

自1.4.10起,ReflectionConverter#canConvert变成这样:

image-20240308145048718.png

不是不防了,而是防御位置变了。

InternalBlackList

参考自:https://www.anquanke.com/post/id/204314#h3-15

版本1.4.10、1.4.11、1.4.12有这个类,其他都没有(确信。

image-20240308145424762.png

用最开始的payload打1.4.11

image-20240308150749109.png

黑名单

image-20240308143953536.png

在XStream构造方法中,有个setupSecurity。

image-20240308140040061.png

在<=1.4.11,这里没有内置黑名单。

image-20240308140503653.png

之后,就开始陆陆续续添加黑名单了。

image-20240308140104472.png

我们来看看哪里检测黑名单

<java.util.HashMap>
<entry>
<string>123</string>
<string>asd</string>
</entry>
</java.util.HashMap>

image-20240308130244482.png

附全版本黑名单

1.4.12,1.4.13

image-20240308140104472.png

两个正则:.*\\$LazyIteratorjavax\\.crypto\\..*

1.4.14

image-20240308140655810.png

1.4.15

image-20240308140903281.png

1.4.16

image-20240308141103998.png

那几个正则的值

image-20240308141255261.png

1.4.17

image-20240308141519538.png

正则

image-20240308142031682.png

1.4.18,黑名单变白名单

image-20240308141818656.png