Java反射机制

Java反射(Reflection)是Java非常重要的动态特性。

  • 通过反射可以获取任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息。
  • 动态创建Java类实例、调用任意的类方法、修改任意类成员变量值等。

Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂。

获取Class对象

java反射操作的是java.lang.Class对象,所有需要先想办法获取到Class对象。
通常使用以下几种方式获取一个类的Class对象:

  1. 类名.Class,如:com.anbai.sec.classloader.TestHelloWorld.class
  2. class.forName("com.anbai.sec.classloader.TestHelloWorld")
  3. classLoader.loadClass("com.anbai.sec.classLoader.TestHelloWorld");

获取数组类型的Class对象,需要注意Java类型的描述符方式,如:

  1. Class<?> doubleArray = Class.forName("[D"); //相当于double[].class
  2. Class<?> cStringArray = Class.forName("[[Ljava.lang.String"); //相当于String[][].class

获取Runtime类Class对象代码片段

  1. String className = "java.lang.Runtime";
  2. Class runtimeClass1 = Class.forName(classname)"
  3. Class runtimeClass2 = java.lang.Runtime.class;
  4. Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(classname);

通过以上任意一种方式就可以获取java.lang.Runtime类的Class对象了,反射调用内部类的时候,需使用$来代替.,如com.anbai.Test类由一个Hello的内部类,调用的时候该类名写成:com.anbai.Test$Hello

反射java.lang.Runtime

java.lang.Runtime有一个exec方法可以执行本地命令,在很多payload中都能看到反射调用Runtime类来执行本地系统命令。
学习如何反射Runtime类也能让我们理解反射的一些基本用法:
不使用反射执行本地命令代码片段

  1. // 输出命令执行结果
  2. System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(),"UTF-8"));

可以使用一行代码完成本地命令执行操作;
使用反射就会比较麻烦,需要间接性的调用Runtimeexec方法。
反射Runtime执行本地命令代码片段

  1. // 获取Runtime类对象
  2. Class runtimeClass1 = Class.forName("java.lang.Runtime");
  3. // 获取构造方法
  4. Constructor constructor = runtimeClass1.getDeclaredConstructor();
  5. constructor.setAccessible(true);
  6. // 创建Runtime类示例,等价于Runtime rt = new Runtime();
  7. Object runtimeMethod = runtimeClass1.getMethod("exec", String.class);
  8. //调用exec方法,等价于 rt.exec(cmd);
  9. Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
  10. // 获取命令执行结果
  11. InputStream in = process.getInputStream();
  12. // 输出命令执行结果
  13. System.out.println(IOUtils.toString(in, "UTF-8"));

反射调用Runtime实现本地命令执行的流程如下:

  1. 反射获取Runtime类对象(class.forName("java.lang.Runtime"))。
  2. 使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的无法直接调用,需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
  3. 获取Runtime类的exec(String)方法(runtimeClass1.getMethod(“exec”, String.class);)。
  4. 调用exec(String)方法(runtimeMethod.invoke(runtimeIntance, cmd);)。

    反射创建类实例

    在Java的任何一个类都必须有一个或多个构造方法,如果代码中没有创建构造方法那么在类编译的时候会自动创建一个无参数的构造方法。
    Runtime类构造方法示例代码片段:

    1. pubic class Runtime {
    2. /** Don't let anyone else instantiate this class */
    3. private Runtime() {}
    4. }

    Runtime类代码注释我们可以看到它本身是不希望除了自身的任何人去创建该实例,因为这个是一个私有的类构造方法,没有办法new一个Runtime类实例,即不能使用Runtime rt = new Runtime();的方式创建Runtime对象,但可用通过反射机制,修改了方法访问权限从而间接的创造出了Runtime对象。

runtimeClass1.getDeclaredConstructorruntimeClass1.getConstructor都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法的时候采用前者去获取构造方法。
如果构造方法有一个或者多个参数的情况下,应在获取构造方法时候传入对应的参数类型数组,如:class.getDeclaredconstructor(String.class, String.class)

如果想获取类的所有构造方法可以使用class.getDeclaredConstructors来获取Constructor数组。

获取到Constructor以后我们可以通过constructor.Insteance()来创建类实例,同理在有参数的情况下应该传入对应的参数值,如:constructor.newInstance("admin", "123456")。当我们没有访问构造方法权限的时应该调用constructor.setAccessible(true)修改访问权限,即可成功创建出类实例。

反射调用类方法

class对象提供了一个获取谋和类的所有的成员所有的方法,也可以通过方法名和方法参数类型类获取指定成员方法。

获取当前类所有的成员方法:

  1. Method[] methods = clazz.getDeclaredMethod()

获取当前类指定的成员方法:

  1. Method method = clazz.getDeclaredMethod("方法名");
  2. Method method = clazz.getDeclaredMethod("方法名" 参数类型如String.class, 多个参数用","号隔开);

getMethodgetDeclaredMethod都能获取到类成员的方法。
区别:
getMethhod只能获取到当前类和父类的所有权限的方法(如:public
getDeclaredMethod能获取到当前类的所有成成员方法(不含父类)

反射调用方法

获取到java.lang.reflect.Method对象可以通过Methodinvke方法来调用类方法。

调用类方法代码片段:

  1. method.invoke(方法实例对象, 方法参数值, 多个参数值用","隔开);

method.invoke的第一个参数必须是类实例对象,如果调用的static方法那么第一个参数值可以传null,在java中金泰调用方法是不是需要有类实例的,因为可以直接类名.方法名(参数)的方式调用。
method.invoke的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么必须严格的依次传入对应的参数类型

反射调用成员变量

Java反射不但可以获取所有的成员变量名称,还可以无视权限修饰符实现修改的值。

获取当前类的所有成员变量

  1. Field fields = clazz.getDeclaredFields();

获取当前类指定的成员变量

  1. Field field = class.getDeclaredField("变量名");

getFieldgetDeclaredField的区别同getMethodgetDeclaredMethod

获取成员变量值

  1. Object obj = field.get(类实例对象);

修改成员变量值

  1. field.set(类实例对象, 修改后的值);

同理,当没有修改的成员变量权限时可以使用:field.setAccessible(true)的方式修改为访问成员变量访问权限。

当需要修改被final关键字修饰的成员变量,那么将需要先修改方法

  1. // 反射获取Field类的modiers
  2. Field modifiers = field.getClass().getDeclaredField("modifiers");
  3. // 设置modifiers修改权限
  4. modifiers.serAccessible(true);
  5. //修改成员变量的Field对象的modifiers值
  6. modifiers.setInt(field, field.getMod);

总结

Java反射采用Java动态性中最重要的体现,利用反射机制可以轻松实现Java类的动态调用。Java的大部分框架都是采用反射机制实现来实现的(如:Spring MVC、ORM框架等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到至关重要的作用。