亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java反射與Fastjson的危險(xiǎn)反序列化詳解

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

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è)名為 macBookProComputer 類(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)注其中的操作,我們首先獲取 carLengthField 對(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)文章

最新評(píng)論