Java反射与Fastjson的危险反序列化
Preface
在前文中,我们介绍了 Java 的基础语法和特性和 fastjson 的基础用法,本文我们将深入学习fastjson的危险反序列化以及预期相关的 Java 概念。
什么是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) 我们逐步进行解析:
modifiers.setInt对modifiers对象进行修改,也就是修改carLength的修饰符。- 首先传入实例,重点在其参数,这里实际是一个位操作,
getmodifiers()方法会返回当前对象的修饰符组合,它是由 Java Modifier 类中定义的值所组合起来的。见下图。

- 那么
~Modifier.FINAL中的~是对该值取反,0x10转换为二进制为0001 0000取反为1110 1111,&对其进行与操作,那么实际上就是在去除FINAL修饰符。 - 最后将其结果修改
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");
}
}
Java反射与Fastjson的危险反序列化的更多相关文章
- Java安全之Fastjson反序列化漏洞分析
Java安全之Fastjson反序列化漏洞分析 首发:先知论坛 0x00 前言 在前面的RMI和JNDI注入学习里面为本次的Fastjson打了一个比较好的基础.利于后面的漏洞分析. 0x01 Fas ...
- storm源码之巧用java反射反序列化clojure的defrecord获取属性值
[原创]storm源码之巧用java反射反序列化clojure的defrecord获取属性值 [原创]storm源码之巧用java反射反序列化clojure的defrecord获取属性值 storm源 ...
- Java基础/利用fastjson反序列化json为对象和对象数组
利用fastjson反序列化json为对象和对象数组 利用 fastjosn 将 .json文件 反序列化为 java.class 和 java.util.List fastjson 是一个性能很好的 ...
- 浅析Java反射机制
目前,在项目中使用Java反射机制(除Spring框架)的地方不多,但为后续准备,简单将最近的反射体会总结如下: 1. 按光学中的反射,可以将java中的反射理解为“镜像”.有以下用途: Java反射 ...
- Java反射在JVM的实现
1. 什么是Java反射,有什么用?反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段.这使反射成为构建灵活应用 ...
- Java进阶(四)Java反射TypeToken解决泛型运行时类型擦除问题
在开发时,遇到了下面这条语句,不懂,然习之. private List<MyZhuiHaoDetailModel> listLottery = new ArrayList<MyZhu ...
- java反射以及动态代理的学习
java反射学习 1)字节码文件的三种获取方式 ①:Object类的getClass()方法:对象.getClass() ②:数据类型的静态的class属性:类名.class ③:通过Class类的静 ...
- java反射之-性能优化
在最近的计划中,打算看看在不使用google protobuf的情况下,在原有的采用jackson作为json序列化工具的基础上,是否可以实现进一步的性能优化.主要是针对list的情况. 测试的时候选 ...
- java代码中fastjson生成字符串和解析字符串的方法和javascript文件中字符串和json数组之间的转换方法
1.java代码中fastjson生成字符串和解析字符串的方法 List<TemplateFull> templateFulls = new ArrayList<TemplateFu ...
- Java 反射机制详解(上)
一.什么是反射 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...
随机推荐
- 使用SQL Server语句统计某年龄段人数占总人数的比例(多层查询语句嵌套-比例分析)
需求:需统计出某个集合内,某个段所占的比例,涉及SELECT查询语句的嵌套,如有疑问可留言. 如下: --按性别进行年度挂号年龄段分析--男SELECT 年龄段,SUM(人数) 数量,cast(cas ...
- three.js 物体要使用光线投射技术,计算是否点击位置与物体有交叉
原生 DOM 还用原生的 DOM 点击事件,要注意开启 pointerEvents CSS3DRenderer 是一个新的渲染器,需要在渲染循环调用并适配 labelRenderer.domEleme ...
- AI 一键生成高清短视频,视频 UP 主们卷起来...
现在短视频越来越火,据统计,2023年全球短视频用户数量已达 10 亿,预计到2027年将突破 24 亿.对于产品展示和用户营销来说,短视频已经成为重要阵地,不管你喜不喜欢它,你都得面对它,学会使用它 ...
- C# winform GDI+ 五子棋 (二):根据博弈算法写的人机AI(抄的别人的)
白棋是ai,最后ai走赢了. 根据博弈算法的一个AI.遍历深度6层,下子很慢.其实我是从别人的代码里复制的算法,改到自己上面用了. 这个博弈算法 class GameAI { /// <summ ...
- acedCommand 之使用镜像命令
ads_name ent; ads_point pt2, pt3; if (acedSSGet(NULL, NULL, NULL, NULL, ent) != RTNORM) { return; } ...
- 什么是Base64算法
HTTP是超文本传输协议,所以HTTP协议中请求.相应都是以ASCII字符方式传输,如果要传输二进制需要经过BASE64或MIME等编码(因为HTTP协议pop3.smtp邮件协议都是针对文本的,而F ...
- 二进制安装Kubernetes(k8s)v1.30.1
二进制安装Kubernetes(k8s)v1.30.1 https://github.com/cby-chen/Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes(k ...
- truffle 框架complie错误。
第一次使用trulle框架编译合约发现编译失败,原因是没有在truffle-config.js文件中加入编译器版本. module.exports = { // See <http://truf ...
- Android 12(S) MultiMedia Learning(二)MediaPlayer Java
Android提供了MediaPlayer这样一个简单易用的音视频java播放接口,通过几个接口调用即可实现音视频播放. 源码位置 http://aospxref.com/android-12.0.0 ...
- 调试YOLOv3/YOLOv5过程中遇到的问题汇总
今日立冬 yolov1 yolo1论文 https://www.bilibili.com/video/BV15w411Z7LG yolo2论文 https://www.bilibili.com/vid ...