基本概念
Java反射是Java非常重要的动态特性,反射的核心是当JVM处于运行状态时才动态加载类,此时对于任意类都能够知道该类的所有的属性和方法,并且能够调用任意一个对象的方法,这种动态获取信息和动态调用对象方法的功能称之为Java的反射机制。通过使用反射,不仅可以获取到任意类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。其中需要注意的是,Java的反序列化问题都基于反射机制。
反射机制流程
在下图中,首先创建了一个类,在javac编译过后会形成class文件,与此同时jvm内存会查找生成的class文件并读入内存中,经过ClassLoader加载,接着会自动创建一个Class对象,里面拥有其成员变量、成员方法、构造方法等,最后是常见的new创建对象。

使用方式
获取Class对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| # 方法一: 已知具体的类, 通过类的class属性获取, 安全性高, 程序性能好, 多用于参数的传递 Class class1 = reflectTestClass.class;
# 方法二: 已知某个类的实例, 调用该实例的getClass方法获取class对象, 多用于对象的获取字节码的方式 ReflectTestClass reflectTestClass = new ReflectTestClass(); Class class2 = reflectTestClass.getClass();
# 方法三: 已知一个类的名称及路径, 且在该类路径下可以通过class类的静态方法forName获取, 需要注意可能抛出ClassNotFoundException, 多用于配置文件 Class class3 = Class.forName("CyberSpace.reflectTestClass");
# 方法四: 利用ClassLoader来获取类 ClassLoader classLoader = this.getClass().getClassLoader(); Class class4 = classLoader.loadClass("CyberSpace.reflectTestClass");
|

获取成员变量Field
1 2 3 4
| java.lang.Class#getFields() ## 获取所有的public修饰的成员变量 java.lang.Class#getField(String) ## 获取指定名称的public修饰的成员变量 java.lang.Class#getDeclaredFields() ## 获取所有的成员变量(不考虑修饰符) java.lang.Class#getDeclaredField(String) ## 获取指定名称的成员变量(不考虑修饰符)
|

获取成员方法Method
1 2 3 4
| java.lang.Class#getMethods() ## 返回所有的public方法, 包括类自身声明的public方法, 父类中的public方法、实现的接口方法等 java.lang.Class#getMethod(String, Class[]) ## 返回该类或接口所声明的public方法 java.lang.Class#getDeclaredMethods() ## 返回该类所有声明方法, 但不包括继承方法 java.lang.Class#getDeclaredMethod(String, Class[]) ## 返回该类指定的声明方法
|

获取构造方法Constructor
1 2 3
| java.lang.Class#getConstructors() ## 返回public修饰的构造函数 java.lang.Class#getConstructor(Class[]) ## 返回匹配和参数配型相符的public修饰的构造函数 java.lang.Class#getDeclaredConstructors() ## 返回匹配和参数配型相符的构造函数(不考虑修饰符)
|

执行命令
Runtime函数有exec方法可以供本地执行命令,在jsp中大部分命令执行的payload都是调用Runtime的exec方法来进行命令执行的。
非反射执行命令
1 2 3 4 5 6 7 8 9 10 11
| import org.apache.commons.io.IOUtils; import java.io.InputStream;
public class EvilClass {
public static void main(String[] args) throws Exception{
InputStream inputStream = Runtime.getRuntime().exec("whoami").getInputStream(); System.out.println(IOUtils.toString(inputStream, "utf-8")); } }
|

反射执行命令
在非反射执行命令中,代码格式基本上是定死的,当需要多次传入参数执行命令时,便可以利用反射来完成需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import org.apache.commons.io.IOUtils; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method;
public class EvilReflectClass {
public static void main(String[] args) throws Exception {
String command = "ipconfig"; Class clazz = Class.forName("java.lang.Runtime"); Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); Object runtimeObject = constructor.newInstance(); Method exec = clazz.getMethod("exec", String.class); Process process = (Process) exec.invoke(runtimeObject, command); InputStream inputStream = process.getInputStream(); String output = IOUtils.toString(inputStream, "gbk"); System.out.println(output); } }
|

特别的,method.invoke中第一个参数必须为类实例对象,当调用的是static方法时该值可以为null(在Java中调用静态方法不需要有类实例,可以直接用类名.方法名进行调用);method.invoke中第二个参数不是必要的,若当前调用方法中无参数时,则第二个参数可以没有,但是若有参数,则必须严格的依次传入对应的参数。