欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java反射與Fastjson的危險反序列化詳解

 更新時間:2024年07月08日 08:57:46   作者:ZywOo  
在?Java?中,Computer.class是一個引用,它表示了?Computer?的字節(jié)碼對象(Class對象),這個對象被廣泛應(yīng)用于反射、序列化等操作中,那么為什么?parseObject?需要這個引用呢,帶著這個問題我們一起通過本文學(xué)習(xí)下吧

Preface

前文中,我們介紹了 Java 的基礎(chǔ)語法和特性和 fastjson 的基礎(chǔ)用法,本文我們將深入學(xué)習(xí)fastjson的危險反序列化以及預(yù)期相關(guān)的 Java 概念。

什么是Java反射?

在前文中,我們有一行代碼 Computer macBookPro = JSON.parseObject(preReceive,Computer.class);

這行代碼是什么意思呢?看起來好像就是我們聲明了一個名為 macBookProComputer 類,它由 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)注其中的操作,我們首先獲取 carLengthField 對象,并設(shè)置其為可讀寫的權(quán)限。

其次獲取該對象的 modifiers 對象,它表示 carLength 被哪些修飾符所修飾。

重點是modifiers.setInt(field2, field2.getModifiers() & ~Modifier.FINAL) 我們逐步進行解析:

  • modifiers.setIntmodifiers 對象進行修改,也就是修改 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)文章

最新評論