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