java反射与fastjson的危险反序列化-kb88凯时官网登录

来自:
时间:2024-07-06
阅读:

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 来接收 class 对象。
  • getdeclaredfields() 是 java 的反射操作,通过 class 对象获得类中所有的属性,包括私有属性,它返回一个 field[] 对象,实际上是一个包含类中所有属性的数组,但它被特定为 field[] 对象。
  • getdeclaredmethods() 同理,获得类中所有的方法(但不包含构造方法),返回一个 methods[] 数组。
  • getdeclaredconstructors() 用于获得类中所有的构造方法,constructor[] 的含义是,constructor 是个泛型类,它的定义是 constructor ,这意味着它适用于任何类型的构造方法,通过使用通配符 表示这个数组接收任何类的构造方法,也就是表示了constructors 这个数组可以用于存储任意类的任意构造方法。
  • 获得了数组后,通过 java 的 for-each 循环遍历数组并打印到屏幕。

运行结果如下。
java反射与fastjson的危险反序列化

反射调用类方法

简要将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。运行结果如下。
java反射与fastjson的危险反序列化

我们将 carlength 使用 final 修饰符进行修饰,此时直接修改 carlength 会报错。如下图。
java反射与fastjson的危险反序列化

但通过反射我们可以修改 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 类中定义的值所组合起来的。见下图。
    java反射与fastjson的危险反序列化

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

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

整段代码的执行结果如下。
java反射与fastjson的危险反序列化

反射执行命令

在 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");
    }
}
返回顶部
顶部
网站地图