写点什么

Java 反射与 Fastjson 的危险反序列化

  • 2024-07-08
    福建
  • 本文字数:4404 字

    阅读完需:约 14 分钟

什么是 Java 反射?


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


这行代码是什么意思呢?看起来好像就是我们声明了一个名为 macBookPro 的 Computer 类,它由 fastjson 的 parseObject 方法将 preReceive 反序列化而来,但 Computer.class 是什么呢?


在 Java 中,Computer.class是一个引用,它表示了 Computer 的字节码对象(Class 对象),这个对象被广泛应用于反射、序列化等操作中。那么为什么 parseObject 需要这个引用呢?首先 fastjson 是不了解类中的情况的,因此它需要一个方法来动态的获得类中的属性,那么 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对象,用于后续的反射操作 Class<?> temp = Car.class; // 获得Car类的所有属性与方法和构造方法 Field[] fields = temp.getDeclaredFields(); Method[] methods = temp.getDeclaredMethods(); Constructor<?>[] constructors = temp.getDeclaredConstructors();
// 通过循环遍历获得类属性 for (Field field : fields){ System.out.println("Field: " + field.getName()); }
// 通过循环遍历获得方法名 for (Method method : methods ) { System.out.println("Methods: " + method.getName()); }
// 通过双循环获得类的构造方法及其方法所需要的参数的数据类型 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()); } }
// 通过反射调用类方法 } 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!"); } }}
复制代码


反射调用类变量


上述代码中,我们有一个公共静态类 Car ,其中包含了私有和公共方法和属性,在主函数中通过反射获取了类的属性和方法以及构造方法,我们逐行分析代码。


  • Class<?> temp = Car.class; 这行代码用于获取 Car 的 Class 对象,Class 对象是整个反射操作的起点。那么 Class<?> 是什么意思呢?其实在这里这个问号指的是 temp 可以接收任意类型的类,我们也可以通过 Class<Car> 来接收 Class 对象。

  • getDeclaredFields() 是 Java 的反射操作,通过 Class 对象获得类中所有的属性,包括私有属性,它返回一个 Field[] 对象,实际上是一个包含类中所有属性的数组,但它被特定为 Field[] 对象。

  • getDeclaredMethods() 同理,获得类中所有的方法(但不包含构造方法),返回一个 Methods[] 数组。

  • getDeclaredConstructors() 用于获得类中所有的构造方法,Constructor<?>[] 的含义是,Constructor 是个泛型类,它的定义是 Constructor<T> ,这意味着它适用于任何类型的构造方法,通过使用通配符 <?> 表示这个数组接收任何类的构造方法,也就是表示了constructors 这个数组可以用于存储任意类的任意构造方法。

  • 获得了数组后,通过 Java 的 for-each 循环遍历数组并打印到屏幕。


运行结果如下。



反射调用类方法


简要将 Demo 中的代码修改如下。

// 通过循环遍历获得方法名
for (Method method : methods) { // 直接调用类的静态方法 if (method.getName().equals("CarType")) { method.invoke(null); } // 通过类的实例调用类方法 if (method.getName().equals("CarAnnounce")){ Car tempCar = new Car(1000,"Richard's car"); method.invoke(tempCar); // 通过反射获得类字段,并修改字段值重新调用方法 Field field = temp.getDeclaredField("carPrice"); field.setAccessible(true); field.set(tempCar, 99999); method.invoke(tempCar); } System.out.println("Methods: " + method.getName());}
复制代码


我们可以通过反射直接调用类的方法,method.invoke(类的实例, 参数, 多个参数用逗号隔开),若是调用静态方法可以传递 null 代替类的实例,但如果调用的方法需要参数,我们需要严格得按照方法传入对应的参数。


我们还可以通过反射修改 private 属性,例如 Demo 中的 carPrice。运行结果如下。



我们将 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);
复制代码


我们来重点关注其中的操作,我们首先获取 carLength 的 Field 对象,并设置其为可读写的权限。

其次获取该对象的 modifiers 对象,它表示 carLength 被哪些修饰符所修饰。


重点是modifiers.setInt(field2, field2.getModifiers() & ~Modifier.FINAL) 我们逐步进行解析:


  1. modifiers.setInt 对 modifiers 对象进行修改,也就是修改 carLength 的修饰符。

  2. 首先传入实例,重点在其参数,这里实际是一个位操作,getmodifiers() 方法会返回当前对象的修饰符组合,它是由 Java Modifier 类中定义的值所组合起来的。见下图。



  1. 那么 ~Modifier.FINAL 中的 ~ 是对该值取反,0x10 转换为二进制为 0001 0000 取反为 1110 1111& 对其进行与操作,那么实际上就是在去除 FINAL 修饰符。

  2. 最后将其结果修改 modifiers 对象,也就是去除了 FINAL 修饰符。


整段代码的执行结果如下。



反射执行命令


在 Java 中,有一个类叫做 java.lang.Runtime ,这个类有一个 exec 方法可以用于执行本地命令。一般情况下我们使用如下的方法执行命令。我们通过 Runtime.getRuntime()方法获得实例,并创建新的 Process 对象,用于执行命令。


Runtime 类是 Java 中的一个特殊类,它负责提供 Java 应用程序与运行时环境(Java 虚拟机)的交互接口。它被设计为单例模式,确保整个应用程序中只有一个 Runtime 实例。这种设计决定了 Runtime 类无法被直接实例化。

package org.example;
public class ExecuteCommandDemo { public static void main(String[] args){ try { // 创建Runtime对象 Runtime temp = Runtime.getRuntime(); Process process = temp.exec("calc.exe");
// 等待命令执行完毕 process.waitFor(); } catch (Exception e) { e.printStackTrace(); } }}
复制代码


而我们同样可以通过反射来执行命令。


首先获取 Runtime 类对象以便后续的反射操作,再从 Runtime 类中获取 getRuntime 方法,通过执行 getRuntime 方法获取实例,再从类中找到 exec 方法,但由于 exec 具有很多重载版本,我们指定使用接收字符串作为参数的方法。最后通过调用 exec 方法,执行命令。

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");
// 等待命令执行完毕 process.waitFor(); } catch (Exception e) { e.printStackTrace(); } }}
复制代码


Fastjson 的危险反序列化


@type 是 fastjson 中的一个特殊注解,它告诉 fastjson 应该将 JSON 字符串转换成哪个 Java 类。这很容易出现安全问题


我们来看下面这段代码,我们定义了一串 json 字符串,想要通过@type注解来将 json 字符串转化为java.lang.Runtime对象,但是 fastjson 在 1.2.24 后默认禁用 autoType 的白名单设置,在默认情况下我们不能任意的将 json 字符串转化为指定的 java 类。


但通过 ParserConfig.getGlobalInstance().addAccept("java.lang") 我们可以在白名单中添加 java.lang 类。


后续的代码就是通过反序列化将其转换为对象(这里的 Object.class 是为了接收转换后的任意对象),再强制转换为 Runtime 对象,转换完成后就和正常调用java.lang.Runtime执行命令相同了。

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"); }}
复制代码


文章转载自:ZywOo

原文链接:https://www.cnblogs.com/RichardLuo/p/18287704/Fastjson_2

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Java反射与Fastjson的危险反序列化_Java_快乐非自愿限量之名_InfoQ写作社区