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ī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中的一個特殊類,它負責(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 后默認禁用 autoType 的白名單設(shè)置,在默認情況下我們不能任意的將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-08
Java反射通過Getter方法獲取對象VO的屬性值過程解析
這篇文章主要介紹了Java反射通過Getter方法獲取對象VO的屬性值過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-02-02
Java數(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-11
Spring Boot集成MyBatis實現(xiàn)通用Mapper的配置及使用
關(guān)于MyBatis,大部分人都很熟悉。MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。這篇文章主要介紹了Spring Boot集成MyBatis實現(xiàn)通用Mapper,需要的朋友可以參考下2018-08-08
SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼
這篇文章主要介紹了SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09

