首页 > 编程语言 >Java反射与Fastjson的危险反序列化

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

时间:2024-07-06 20:41:28浏览次数:19  
标签:Fastjson Runtime Java Car class java 序列化 Class

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");
    }
}

标签:Fastjson,Runtime,Java,Car,class,java,序列化,Class
From: https://www.cnblogs.com/RichardLuo/p/18287704/Fastjson_2

相关文章

  • java09
    类与对象的关系对象是具体的实例需要通过new来执行类是一种抽象的概念我们在写程序是最好只要有一个main方法构造器构造器实例化可以给属性一个初始值,new的本质是在调用构造器构造器也分为有参构造和无参构造,无参构造有时候很重要,因为在使用有参构造是,用户不一定会给与参数......
  • JAVA零基础小白自学日志——第八天
    文章目录1.封装2.变量的作用域3.构造函数今日提要:封装(其实说白了,封装就是对访问范围的限制,不用因为名字就觉得高大上了)哦,对了,还要顺便讲个构造函数我在两天的学习里都提到了封装,而且是很严肃的,那你就应该知道这个封装对于Java程序非常重要;把昨天最后一个狗狗的......
  • JAVA零基础小白自学日志——第七天
    文章目录一、类1.类名2.这是啥东西?虽然在类里,但是不在构造方法里面,也不在语句块里,它是谁?它要干啥?3.方法二、对象(重头戏来喽)1.类与对象的关系2.类不是对象,对象由类构造3.类和对象到底在程序里是什么样的今日提要:类和对象(什么?你说你没对象!?去去去,别打岔,我这对象不是你......
  • 深入Java:JSON解析与操作的艺术
    哈喽,大家好,我是木头左!一、初识JSON:数据格式的优雅舞者在现代Web开发中,JSON(JavaScriptObjectNotation)以其轻量级和易于阅读的特点成为了数据交换的首选格式。它基于JavaScript的一个子集,采用完全独立于语言的文本格式,使得任何编程语言都能够轻松解析和生成。JSON的基本结构......
  • 3101.力扣每日一题7/6 Java(接近100%解法)
    博客主页:音符犹如代码系列专栏:算法练习关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞......
  • Java计算机毕业设计疫情防控形势下的高校食堂订餐管理系统(开题+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着新冠疫情的全球蔓延,公共卫生安全成为了前所未有的重大挑战。高校作为人员密集且流动性大的场所,其食堂管理面临着巨大的疫情防控压力。传统的食堂......
  • Java计算机毕业设计食品安全监督平台的设计与实现(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着社会经济的快速发展和人民生活水平的不断提升,食品安全问题日益成为社会关注的焦点。近年来,食品安全事件频发,不仅严重损害了消费者的身体健康,也影......
  • 从零学习的JAVAday1~day7
    作为一个刚要迈入大二的预备程序员,已经学习过了c语言和c++的部分知识,在暑假期间满怀期待的开始Java的学习,希望一个暑假可以对Java的了解加深一些。学习Java首先要学习windows电脑的cmd命令:同时点击键盘上面的win+r键输入cmd即可进入默认的cmd面版,然后我们就可以输入一些命令:比......
  • 面试官:Java线程可以无限创建吗?
    哈喽,大家好......
  • 学生Java学习路程-1
    ok,到了一周一次的总结时刻,我大致会有下面几个方面的论述:1.这周学习了Java的那些东西2.这周遇到了什么苦难3.未来是否需要改进方法等几个方面阐述我的学习路程。这周我主要进行了java开发程序EclipseForJavaDeveloper跟jdk的安装,其中在安装jdk过程中,因为没有安装在C盘中,所以......