反射

前言

学习Java的反射机制是为了理解Apache Commons Collections中的反序列化漏洞做准备的。

反射概念

反射是一种间接操作目标对象的机制,允许程序在运行时获取类的信息,并且在运行时动态地创建对象、调用方法、访问字段等等;对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的方法/访问属性。


实质就是得到一个Class对象后,反向获取Class对象的对象。

Class类、Class对象与class关键字

Class类是java中的一个类,位于java.lang包中。它提供了用于获取类信息和操作类或对象的属性和方法的方法。image-20240514205516919

Class对象是JVM在运行时保留的每个类的描述信息。Class对象包含了该类的所有信息,包括类的名称、属性、方法、构造函数等。Class对象可以通过Class类的各种方法获取。

class是java的关键字,用于声明类。


对Class类解读:

我们通常认为类是对象的抽象和集合,Class就相当于是对类的抽象和集合。
也可以认为对象是类的实例,类是Class的实例。

获取Class对象

通常有以下几种方法获取一个类的Class对象

Class.forName()

1
Class<?> aClass = Class.forName("Test");     如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取

Test.class

1
Class<?> bClass = Test.class;    如果已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就可以直接拿它的class属性。

obj.getClass()

1
2
Test cClass= new Test();
Class<? extends Test> aClass1 = cClass.getClass(); 如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过 obj.getClass() 来获取它的类

image-20240514215718317

但在这三种获取CLass类方式中,我们一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。

实例化对象

获取到Class之后,实例化对象,newInstance()方法调用无参的构造器创建对象。

image-20240516090748055

(Class类中的newInstance()方法)

image-20240516102707921

java.lang.relect.Constructor类里也有一个newInstance()方法可以创建对象,该方法和Class类中的newInstance()方法很像,但是相比之下,Constructor类的newInstance()方法更加强大,我们可以通过这个newInstance()方法调用有参数的和私有的构造方法。

image-20240516102944537

这时有个问题,比如一个类有很多的构造方法,我们怎么能找到我们想要的构造方法呢?我们可以使用getConstructor(),它根据提供的参数类型来定位特定的构造函数。可以从getConstructor()的函数声明中看到,参数类型是Class可变长参数。

image-20240516094652144

因为Test类中有两个属性,Sting nameint agegetConstructor()填入String.classint.class,发现报错了,报错信息提示没有这个方法

image-20240516091359629

可以看到Test类中age属性是私有的,这时使用getDeclaredConstructor()方法。

image-20240516090723742

设置访问权限为true。就可以通过有参构造函数实例化对象。

image-20240516091419131

获取类的属性

通过getFields()可以获取属性,但是发现这里只获取了name,age并没有被获取,原因其实和上面类似,必须用getDeclaredFields()并设置访问权限。

image-20240516095310090

image-20240516095330837

set()方法通常和getField()搭配使用,set()方法是Field类的一部分,用于通过反射机制设置对象的字段值,具体一点就是Field对象表示类中的一个成员变量,set()方法允许修改这个字段的值,即使该字段是私有的。在使用之前,通常需要调用setAccessible(true)来绕过Java的访问控制检查。

image-20240516101853830

age属性是私有的

image-20240516102050549

(通过getDeclaredField()设置访问权限)

image-20240516102124715

调用类的方法

Test类有public Hello方法和Private prHello方法。

image-20240516113808489

通过getMethods()可以获取公共方法。

image-20240516112756232

通过getDeclaredMethods()可以获取公共方法和私有方法

image-20240516112826908

getMethod()通常和invoke()搭配,invoke()可以动态地在运行时调用对象的方法。其实也不难理解,我们通过getMethod()反射获得一个方法后,肯定需要指定是哪个类,并且指明执行的参数。

image-20240516131226025

getMethod()调用一下Test类中的Hello()方法,可以发现它调用的是无参的方法,和实例化对象的情况类似,报错了,需要指明它的类型,否则调用无参的方法。

image-20240516130413038

(Class类中的getMethod()方法)

image-20240516113750554

image-20240516130817172

小练习,反射弹计算器

正常情况下咱们弹计算器image-20240516133359336

反射写法

image-20240516141618708

因为 getRuntime()是静态方法,属于类,所以getRuntime.invoke();不需要指定对象。

1
2
//调用 getRuntime 方法,获得 Runtime 类的实例
Object runtimeInstance = getRuntime.invoke(null);

image-20240516143222774

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");

//获取Runtime 类的 Class 对象
Class<?> clazz = Class.forName("java.lang.Runtime");
//获取 Runtime 类中名为 getRuntime 的方法的引用
Method getRuntime = clazz.getMethod("getRuntime");
//调用 getRuntime 方法,获得 Runtime 类的实例
Object runtimeInstance = getRuntime.invoke(null);
//getMethod() 方法获取 exec 方法的引用
Method exec = clazz.getMethod("exec", String.class);
//用 invoke() 方法调用 exec
exec.invoke(runtimeInstance, "calc");


}
}

参考链接

JAVA反射

java反射机制

JAVA反序列化 - 反射机制

JAVA安全基础(二)– 反射机制


反射
https://sp4rks3.github.io/2024/05/16/JAVA安全/反序列化/反射/
作者
Sp4rks3
发布于
2024年5月16日
许可协议