Java反射與Fastjson的危險反序列化詳解
Preface
在前文中,我們介紹了 Java 的基礎(chǔ)語法和特性和 fastjson 的基礎(chǔ)用法,本文我們將深入學(xué)習(xí)fastjson的危險反序列化以及預(yù)期相關(guān)的 Java 概念。
什么是Java反射?
在前文中,我們有一行代碼 Computer macBookPro = JSON.parseObject(preReceive,Computer.class);
這行代碼是什么意思呢?看起來好像就是我們聲明了一個名為 macBookPro
的 Computer
類,它由 fastjson 的 parseObject 方法將 preReceive
反序列化而來,但 Computer.class
是什么呢?
在 Java 中,Computer.class
是一個引用,它表示了 Computer
的字節(jié)碼對象(Class對象),這個對象被廣泛應(yīng)用于反射、序列化等操作中。那么為什么 parseObject 需要這個引用呢?首先 fastjson 是不了解類中的情況的,因此它需要一個方法來動態(tài)的獲得類中的屬性,那么 Java 的反射機制提供了這個功能。
Java reflect demo
我們先看一個 Java 反射的 Demo。
??????? package org.example; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class JavaReflectDemo { public static void main(String[] args){ // 獲取Car類的Class對象,用于后續(xù)的反射操作 Class<?> temp = Car.class; // 獲得Car類的所有屬性與方法和構(gòu)造方法 Field[] fields = temp.getDeclaredFields(); Method[] methods = temp.getDeclaredMethods(); Constructor<?>[] constructors = temp.getDeclaredConstructors(); // 通過循環(huán)遍歷獲得類屬性 for (Field field : fields){ System.out.println("Field: " + field.getName()); } // 通過循環(huán)遍歷獲得方法名 for (Method method : methods ) { System.out.println("Methods: " + method.getName()); } // 通過雙循環(huán)獲得類的構(gòu)造方法及其方法所需要的參數(shù)的數(shù)據(jù)類型 for (Constructor<?> constructor : constructors) { System.out.println("Constructor:" + constructor.getName()); Class<?>[] constructorParameterType = constructor.getParameterTypes(); for (Class<?> parameterType : constructorParameterType) { System.out.println("Parameter type is:" + parameterType.getName()); } } // 通過反射調(diào)用類方法 } public static class Car{ private int carLength; public String carName; private int carPrice = 50000; public Car(int carLength, String carName,int carPrice){ this.carLength = carLength; this.carName = carName; this.carPrice = carPrice; } private void CarAnnounce() { System.out.println("China Car! Best Car!"); System.out.println("The Car Price is " + this.carPrice); System.out.println("The Car Length is " + this.carLength); } private void CarType(){ System.out.println("This function is still under development!"); } } }
???????反射調(diào)用類變量
上述代碼中,我們有一個公共靜態(tài)類 Car
,其中包含了私有和公共方法和屬性,在主函數(shù)中通過反射獲取了類的屬性和方法以及構(gòu)造方法,我們逐行分析代碼。
Class<?> temp = Car.class;
這行代碼用于獲取Car
的 Class 對象,Class 對象是整個反射操作的起點。那么Class<?>
是什么意思呢?其實在這里這個問號指的是temp
可以接收任意類型的類,我們也可以通過Class<Car>
來接收 Class 對象。getDeclaredFields()
是 Java 的反射操作,通過 Class 對象獲得類中所有的屬性,包括私有屬性,它返回一個Field[]
對象,實際上是一個包含類中所有屬性的數(shù)組,但它被特定為Field[]
對象。getDeclaredMethods()
同理,獲得類中所有的方法(但不包含構(gòu)造方法),返回一個Methods[]
數(shù)組。getDeclaredConstructors()
用于獲得類中所有的構(gòu)造方法,Constructor<?>[]
的含義是,Constructor
是個泛型類,它的定義是Constructor<T>
,這意味著它適用于任何類型的構(gòu)造方法,通過使用通配符<?>
表示這個數(shù)組接收任何類的構(gòu)造方法,也就是表示了constructors
這個數(shù)組可以用于存儲任意類的任意構(gòu)造方法。- 獲得了數(shù)組后,通過 Java 的 for-each 循環(huán)遍歷數(shù)組并打印到屏幕。
運行結(jié)果如下。
反射調(diào)用類方法
簡要將Demo中的代碼修改如下。
// 通過循環(huán)遍歷獲得方法名 for (Method method : methods) { // 直接調(diào)用類的靜態(tài)方法 if (method.getName().equals("CarType")) { method.invoke(null); } // 通過類的實例調(diào)用類方法 if (method.getName().equals("CarAnnounce")){ Car tempCar = new Car(1000,"Richard's car"); method.invoke(tempCar); // 通過反射獲得類字段,并修改字段值重新調(diào)用方法 Field field = temp.getDeclaredField("carPrice"); field.setAccessible(true); field.set(tempCar, 99999); method.invoke(tempCar); } System.out.println("Methods: " + method.getName()); }
我們可以通過反射直接調(diào)用類的方法,method.invoke(類的實例, 參數(shù), 多個參數(shù)用逗號隔開)
,若是調(diào)用靜態(tài)方法可以傳遞 null
代替類的實例,但如果調(diào)用的方法需要參數(shù),我們需要嚴(yán)格得按照方法傳入對應(yīng)的參數(shù)。
我們還可以通過反射修改 private
屬性,例如 Demo 中的 carPrice
。運行結(jié)果如下。
我們將 carLength
使用 final
修飾符進行修飾,此時直接修改 carLength
會報錯。如下圖。
但通過反射我們可以修改 final
修飾符修飾后的屬性。代碼如下。
Field field2 = temp.getDeclaredField("carLength"); field2.setAccessible(true); Field modifiers = field2.getClass().getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(field2, field2.getModifiers() & ~Modifier.FINAL); field2.set(tempCar, 7777); method.invoke(tempCar);
我們來重點關(guān)注其中的操作,我們首先獲取 carLength
的 Field
對象,并設(shè)置其為可讀寫的權(quán)限。
其次獲取該對象的 modifiers
對象,它表示 carLength
被哪些修飾符所修飾。
重點是modifiers.setInt(field2, field2.getModifiers() & ~Modifier.FINAL)
我們逐步進行解析:
modifiers.setInt
對modifiers
對象進行修改,也就是修改carLength
的修飾符。- 首先傳入實例,重點在其參數(shù),這里實際是一個位操作,
getmodifiers()
方法會返回當(dāng)前對象的修飾符組合,它是由 Java Modifier 類中定義的值所組合起來的。見下圖。
- 那么
~Modifier.FINAL
中的~
是對該值取反,0x10
轉(zhuǎn)換為二進制為0001 0000
取反為1110 1111
,&
對其進行與操作,那么實際上就是在去除FINAL
修飾符。 - 最后將其結(jié)果修改
modifiers
對象,也就是去除了FINAL
修飾符。
整段代碼的執(zhí)行結(jié)果如下。
反射執(zhí)行命令
在 Java 中,有一個類叫做 java.lang.Runtime
,這個類有一個 exec
方法可以用于執(zhí)行本地命令。一般情況下我們使用如下的方法執(zhí)行命令。我們通過Runtime.getRuntime()方法獲得實例,并創(chuàng)建新的Process對象,用于執(zhí)行命令。
Runtime類是Java中的一個特殊類,它負(fù)責(zé)提供Java應(yīng)用程序與運行時環(huán)境(Java虛擬機)的交互接口。它被設(shè)計為單例模式,確保整個應(yīng)用程序中只有一個Runtime實例。這種設(shè)計決定了Runtime類無法被直接實例化。
package org.example; public class ExecuteCommandDemo { public static void main(String[] args){ try { // 創(chuàng)建Runtime對象 Runtime temp = Runtime.getRuntime(); Process process = temp.exec("calc.exe"); // 等待命令執(zhí)行完畢 process.waitFor(); } catch (Exception e) { e.printStackTrace(); } } }
而我們同樣可以通過反射來執(zhí)行命令。
首先獲取Runtime類對象以便后續(xù)的反射操作,再從Runtime類中獲取getRuntime方法,通過執(zhí)行g(shù)etRuntime方法獲取實例,再從類中找到 exec
方法,但由于 exec
具有很多重載版本,我們指定使用接收字符串作為參數(shù)的方法。最后通過調(diào)用 exec
方法,執(zhí)行命令。
package org.example; import java.lang.reflect.Method; public class ExecuteCommandDemo { public static void main(String[] args){ try { Class <?> reflectExec = Class.forName("java.lang.Runtime"); Method getruntimeMethod = reflectExec.getMethod("getRuntime"); Object runtimeInstance = getruntimeMethod.invoke(null); Method execMethod = reflectExec.getMethod("exec", String.class); Process process = (Process) execMethod.invoke(runtimeInstance, "calc.exe"); // 等待命令執(zhí)行完畢 process.waitFor(); } catch (Exception e) { e.printStackTrace(); } } }
???????Fastjson的危險反序列化
@type
是fastjson中的一個特殊注解,它告訴 fastjson 應(yīng)該將 JSON 字符串轉(zhuǎn)換成哪個 Java 類。這很容易出現(xiàn)安全問題。
我們來看下面這段代碼,我們定義了一串json字符串,想要通過@type
注解來將json字符串轉(zhuǎn)化為java.lang.Runtime
對象,但是 fastjson在 1.2.24 后默認(rèn)禁用 autoType
的白名單設(shè)置,在默認(rèn)情況下我們不能任意的將json字符串轉(zhuǎn)化為指定的java類。
但通過 ParserConfig.getGlobalInstance().addAccept("java.lang")
我們可以在白名單中添加 java.lang
類。
后續(xù)的代碼就是通過反序列化將其轉(zhuǎn)換為對象(這里的Object.class是為了接收轉(zhuǎn)換后的任意對象),再強制轉(zhuǎn)換為Runtime對象,轉(zhuǎn)換完成后就和正常調(diào)用java.lang.Runtime
執(zhí)行命令相同了。
package org.example; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; public class FastjsonDangerousDeserialization { public static void main(String[] args) throws Exception{ String json = "{\"@type\":\"java.lang.Runtime\"}"; ParserConfig.getGlobalInstance().addAccept("java.lang"); Runtime runtime = (Runtime) JSON.parseObject(json, Object.class); runtime.exec("calc.exe"); } }
到此這篇關(guān)于Java反射與Fastjson的危險反序列化的文章就介紹到這了,更多相關(guān)Java反射與Fastjson內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一篇文章帶了解如何用SpringBoot在RequestBody中優(yōu)雅的使用枚舉參數(shù)
這篇文章主要介紹了SpringBoot中RequestBodyAdvice使用枚舉參數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08Java反射通過Getter方法獲取對象VO的屬性值過程解析
這篇文章主要介紹了Java反射通過Getter方法獲取對象VO的屬性值過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-02-02Java數(shù)據(jù)結(jié)構(gòu)之棧的基本定義與實現(xiàn)方法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之棧的基本定義與實現(xiàn)方法,簡單描述了數(shù)據(jù)結(jié)構(gòu)中棧的功能、原理,并結(jié)合java實例形式分析了棧的基本定義與使用方法,需要的朋友可以參考下2017-10-10解決Springboot項目打包后的頁面丟失問題(thymeleaf報錯)
這篇文章主要介紹了解決Springboot項目打包后的頁面丟失問題(thymeleaf報錯),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11Spring Boot集成MyBatis實現(xiàn)通用Mapper的配置及使用
關(guān)于MyBatis,大部分人都很熟悉。MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。這篇文章主要介紹了Spring Boot集成MyBatis實現(xiàn)通用Mapper,需要的朋友可以參考下2018-08-08SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼
這篇文章主要介紹了SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09