Fastjson系列四——1.2.25-1.2.47反序列化漏洞(无需开启AutoType)
/0x01 影响版本
Fastjson 1.2.x系列的1.2.25-1.2.47版本。
0x02 限制
主要是JDK版本对于JDNI注入的限制,基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191。
0x03 复现利用
本次Fastjson反序列化漏洞也是基于checkAutoType()函数绕过的,并且无需开启AutoTypeSupport,大大提高了成功利用的概率。
绕过的大体思路是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测。因此将payload分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkAutoType()就会抛出异常终止程序。
Demo如下,无需开启AutoTypeSupport,本地Fastjson用的是1.2.47版本:
1 | public class JdbcRowSetImplPoc { |
此外,还需要开启RMI服务或LDAP服务以及放置恶意类的Web服务,具体可参考之前的Fastjson系列文章即可。
运行能成功弹计算器:
这里,我们看看PoC是怎么写的:
1 | { |
可以看到实际上还是利用了com.sun.rowset.JdbcRowSetImpl这条利用链来攻击利用的,因此除了JDK版本外几乎没有限制。
但是如果目标服务端开启了AutoTypeSupport呢?经测试发现:
- 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
- 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;
0x04 调试分析
下面我们来调试分析下该PoC为啥会成功。
不受AutoTypeSupport影响的版本
不受AutoTypeSupport影响的版本为1.2.33-1.2.47,本次调试的是1.2.47版本。
未开启AutoTypeSupport时
在调用DefaultJSONParser.parserObject()函数时,其会对JSON数据进行循环遍历扫描解析。
在第一次扫描解析中,进行checkAutoType()函数,由于未开启AutoTypeSupport,因此不会进入黑白名单校验的逻辑;由于@type执行java.lang.Class类,该类在接下来的findClass()函数中直接被找到,并在后面的if判断clazz不为空后直接返回:
往下调试,调用到MiscCodec.deserialze(),其中判断键是否为”val”,是的话再提取val键对应的值赋给objVal变量,而objVal在后面会赋值给strVal变量:
接着判断clazz是否为Class类,是的话调用TypeUtils.loadClass()加载strVal变量值指向的类:
在TypeUtils.loadClass()函数中,成功加载com.sun.rowset.JdbcRowSetImpl类后,就会将其缓存在Map中:
在扫描第二部分的JSON数据时,由于前面第一部分JSON数据中的val键值”com.sun.rowset.JdbcRowSetImpl”已经缓存到Map中了,所以当此时调用TypeUtils.getClassFromMapping()时能够成功从Map中获取到缓存的类,进而在下面的判断clazz是否为空的if语句中直接return返回了,从而成功绕过checkAutoType()检测:
开启AutoTypeSupport时
由前面知道,开启AutoTypeSupport后,在checkAutoType()函数中会进入黑白名单校验的代码逻辑。
在第一部分JSON数据的扫描解析中,由于@type指向java.lang.Class,因此即使是开启AutoTypeSupport先后进行白名单、黑名单校验的情况下都能成功通过检测,之后和前面的一样调用findClass()函数获取到Class类:
关键在于第二部分JSON数据的扫描解析。第二部分的@type指向的是利用类”com.sun.rowset.JdbcRowSetImpl”,其中的”com.sun.”是在denyList黑名单中的,但是为何在检测时能成功绕过呢?
我们调试发现,逻辑是先进行白名单再进行黑名单校验,在黑名单校验的if判断条件中是存在两个必须同时满足的条件的:
1 | if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) { |
第一个判断条件Arrays.binarySearch(denyHashCodes, hash) >= 0
是满足的,因为我们的@type包含了黑名单的内容;关键在于第二个判断条件TypeUtils.getClassFromMapping(typeName) == null
,这里由于前面已经将com.sun.rowset.JdbcRowSetImpl类缓存在Map中了,也就是说该条件并不满足,导致能够成功绕过黑名单校验、成功触发漏洞。
受AutoTypeSupport影响的版本
受AutoTypeSupport影响的版本为1.2.25-1.2.32,本次调试的是1.2.25版本。
开启AutoTypeSupport时
我们在开启AutoTypeSupport之后,会利用失败,报如下错:
1 | Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. com.sun.rowset.JdbcRowSetImpl |
调试发现,在第一部分JSON数据的解析中,checkAutoType()函数的处理过程和前面是差不多的。能够成功通过该函数的检测,因此问题不在这,继续往下调试。
在第二部分JSON数据的解析中,@type指向的”com.sun.rowset.JdbcRowSetImpl”在checkAutoType()函数中会被dentList黑名单中的”com.sun.”匹配到,因此会直接报错显示不支持:
可以明显看到,第一个if语句是白名单过滤,第二个if语句是黑名单过滤,其中黑名单过滤的if语句中的判断条件和前面的不受影响的版本的不一样,对比下是少了个判断条件,即TypeUtils.getClassFromMapping(typeName) == null
。
未开启AutoTypeSupport时
当不开启AutoTypeSupport时就不会进入该黑白名单校验的代码逻辑中,就不会被过滤报错。
这里,我们换个不受AutoTypeSupport影响的且未使用哈希黑名单的版本来方便我们进行对比查看,这里选了1.2.33,看下checkAutoType()中对应的代码:
对比黑名单校验的if判断语句条件就知道了,为什么后面的版本不受影响,那是因为通过&&
多添加了一个判断条件TypeUtils.getClassFromMapping(typeName) == null
,但是第二部分JSON内容中的类已经通过第一部分解析的时候加载到Map中缓存了,因此该条件不成立从而成功绕过:
1 | // 受AutoTypeSupport影响的版本 |
0x05 补丁分析
1.2.48中的修复措施是,在loadClass()时,将缓存开关默认置为False,所以默认是不能通过Class加载进缓存了。同时将Class类加入到了黑名单中。
运行会报错:
1 | Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. com.sun.rowset.JdbcRowSetImpl |
调试分析,在调用TypeUtils.loadClass()时中,缓存开关cache默认设置为了False,对比下两个版本的就知道了。
1.2.48版本:
1.2.47版本:
导致目标类并不能缓存到Map中了:
因此,即使未开启AutoTypeSupport,但com.sun.rowset.JdbcRowSetImpl类并未缓存到Map中,就不能和前面一样调用TypeUtils.getClassFromMapping()来加载了,只能进入后面的代码逻辑进行黑白名单校验被过滤掉:
OK,19年的Fastjson反序列化漏洞就分析到这,后面是Fastjson系列的最后一篇了,说下检测与防御方法、高版本JDK绕过等等。