参考:
前言
本文没有漏洞分析,只有反序列化流程分析与各版本的防御手段总结。
其中关于防御手段,我找的文章没有对我胃口的,于是作了总结, 什么版本哪里防,怎么防,十分甚至九分清楚。
防御手段清楚了,才知道什么版本用什么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) { |
环境
版本1.4.6
public static void main(String[] args) throws Exception { |
payload.xml文件
<sorted-set> |
main
public static void main(String[] args) throws Exception { |
分析
要搞明白的问题:
1、哪里创建对象
2、哪里为对象的属性赋值
断点打在ProcessBuilder#start
来到TreeMapConverter#populateTreeMap
可以看到,sortedMap里有创建出来的对象,所以要从sortedMap生成的地方进入。
那就是上面的putCurrentEntryIntoMap
,断点打在这里。
随后从这里一直深入,就可以看到创建各个对象的过程。
这里正在转换最外层的set集合。
这里正在转换set集合里面的dynamic-proxy
这里正在转换dynamic-proxy的interface
这里在为dynamic-proxy的target属性赋值
然后后面就是创建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可以打,不知为何。
设置canConvert
用上面的payload打1.4.7会有这个错误
修复思路:由于exp主要用到了java.beans.EventHandler,那么令他的converter不能convert即可。对应要点概要第6点。
ReflectionConverter
首先根据上面的报错栈,找到EventHandler的converter是哪个,然后看两个版本的converter差别在哪。
1.4.6
1.4.7
所以原本能convert EventHandler的converter现在不能converter了。
自1.4.10起,ReflectionConverter#canConvert
变成这样:
不是不防了,而是防御位置变了。
InternalBlackList
参考自:https://www.anquanke.com/post/id/204314#h3-15
版本1.4.10、1.4.11、1.4.12有这个类,其他都没有(确信。
用最开始的payload打1.4.11
黑名单
在XStream构造方法中,有个setupSecurity。
在<=1.4.11,这里没有内置黑名单。
之后,就开始陆陆续续添加黑名单了。
我们来看看哪里检测黑名单
<java.util.HashMap> |
附全版本黑名单
1.4.12,1.4.13
两个正则:.*\\$LazyIterator
,javax\\.crypto\\..*
1.4.14
1.4.15
1.4.16
那几个正则的值
1.4.17
正则
1.4.18,黑名单变白名单