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

Preface

在前文中,我们介绍了 Java 的基础语法和特性和 fastjson 的基础用法,本文我们将深入学习fastjson的危险反序列化以及预期相关的 Java 概念。

什么是Java反射?

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

这行代码是什么意思呢?看起来好像就是我们声明了一个名为 macBookProComputer 类,它由 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);

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

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

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

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

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

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

  4. 最后将其结果修改 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");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/739517.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Win10双屏设置 之 鼠标不能从中间划过 问题解决

Win10双屏设置 之 鼠标不能从中间划过解决-百度经验 (baidu.com)

比赛获奖的武林秘籍:03 好的创意选取-获得国奖的最必要前提

本文主要介绍了大学生电子计算机类比赛和创新创业类比赛创意选取的重要性,并列举了好的创意选取和坏的创意选取的例子,同时说明了好的创意选取具有哪些特点,同时对常见的创意选取途径与来源进行了基本介绍。比赛获奖的武林秘籍:03 好的创意选取-获得国奖的最必要前提 摘要 …

阶段测试

Sre网络班阶段测试 一:用sed 命令修改/etc/fstab文件,删除文件中的空行,注释行,并保留文件备份(7分) 答案写这里:二: 用 find 命令查找出 /var/ 目录中大于1M且以db结尾的文件(7分) 答案写这里:三: 先判断当前主机是否安装了nginx包,如果没安装,则执行命令安装,…

时间序列分析专题——利用SPSS专家建模器进行建模

SPSS的专家建模器可以自动识别数据,给出最适合的模型,本章通过三个例题介绍如何使用SPSS实现时间序列分析。由于本人对时间序列分析的理解尚浅,做出模型后在论文上的呈现形式需要取查阅资料,以便更好地在论文上呈现 在此之前,我们还需要了解时间序列分析的一些基础的名词 …

如何在ubuntu上设置清华源

如何在ubuntu上设置清华源 apt介绍 apt(Advanced Packaging Tool)是一个在 Debian 和 Ubuntu 中的 Shell 前端软件包管理器。 apt 命令提供了查找、安装、升级、删除某一个、一组甚至全部软件包的命令,而且命令简洁而又好记。 apt 命令执行需要超级管理员权限(root)。 操作 …

c++ u7-02-高精度乘法

本节课作业: 链接:https://pan.baidu.com/s/13-FC86jSHGziRDA8lqzimg?pwd=owv1 提取码:owv1高精度乘法    #include<iostream> #include<cstdio> #include<cstring> using namespace std; string x , y; int a[50010] , b[50010] , c[50010…

node-red的基本指令

1. inject->debug输入到输出,调试结果在右边如果选择时间戳的话,可以选择立即执行,或者周期性,持续执行inject除了时间戳还有一些其他输入项可以选择inject选择json文件输出写好json文件之后点击格式化json,可以校对文件格式payload.number可以让输出只输出number的内容…

lombokjunit

lombok&junit 1 lombok先去官网或者maven仓库下载jar包https://mvnrepository.com/导入第三方包到项目中右键lib文件夹,点击add as library默认jvm不解析第三方注解,需要手动开启使用//@Setter // 生成set方法 1 //@Getter // 生成get方法 2 //@ToString // 生…

日期类异常类

日期类&异常类 作业:千位数字相乘 public static void main(String[] args) {// 两个千位数字相乘int[] arr1 = {7,8,9,9,8,9};int[] arr2 = {7,9,8,9,6,8};// 定义结果的数组int[] result = new int[12];for (int i = 0; i < arr1.length; i++) {for (int j = 0; j <…

包装类数学类位运算

包装类&数学类&位运算 1 包装类 把基本数据类型包装成引用数据类型byte short int long float double char boolean voidByte Short Integer Long Float Double Character Boolean VoidVoidVoid类构造方法是私有的,所以不能创建对象。并且Void是一个最终类,没有子类。…

EtherCAT转Profinet网关配置说明第二讲:上位机软件配置

EtherCAT协议转Profinet协议网关模块(XD-ECPNS20),不仅可以实现数据之间的通信,还可以实现不同系统之间的数据共享。EtherCAT协议转Profinet协议网关模块(XD-ECPNS20)具有高速传输的特点,因此通过EtherCAT转Profinet网关实现数据传输和控制时速度的提升。在大规模的工业…