Java反射机制
Java反射(Reflection )是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods )、成员变量(Fields )、构造方法(Constructors )等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂。
获取Class对象
Java反射操作的是java.lang.Class 对象,所以我们需要先想办法获取到Class对象,通常我们有如下几种方式获取一个类的Class对象:
Class.forName("ClassName"); ClassLoader.loadClass("ClassName"); //实际使用时需要指定具体的加载器如ClassLoader.getSystemClassLoader() ,再调用loadClass方法 ClassName.class; //类已经被加载,只是获取其java.lang.Class 对象obj.getClass(); //上下文中存在某个类的实例obj时可用
Class.forName(String) 是最常用的的获取Class方式,forname() 有两个方法重载:
-
public static Class<?> forName(String className)
-
public static Class<?> forName(String name,
boolean initialize,
ClassLoader loader)
第一种方法可以理解为第二种方法的一个封装,调用此方法等效于第二种。
Class.forName("Foo")
相当于:
Class.forName("Foo", true, this.getClass().getClassLoader())
值得注意的是第二种方法的第二、三个参数,第二个参数表示是否初始化,第三个参数是ClassLoader (在上一节内容中所提到的)。
第二个参数initialize指的初始化 可以理解为类的初始化,在上一节当中Class.forname() 的例子中也提到了在类初始化 时static{} 语句块被调用(编写恶意类,将恶意代码放入static语句块即可加载),p神在Java安全漫谈当中给出的例子展现了三个“初始化”方法之间的区别,以及调用顺序。
另一个方法ClassLoader.loadClass(String) 也有一个方法重载和上面一样
Class<?> loadClass(String name)
Class<?> loadClass(String name,
boolean resolve)
第一个 ( Class.forName(); ) 将:
- 使用加载调用此代码的类的类加载器
- 初始化类(也就是说,所有静态初始化程序都将运行)
另一个 ( ClassLoader.getSystemClassLoader().loadClass(); ) 将:
实例——获取Runtime类Class对象代码片段:
String className = "java.lang.Runtime";
Class runtimeClass1 = Class.forName(className);
Class runtimeClass2 = ClassLoader.getSystemClassLoader().loadClass(className);
Class runtimeClass3 = java.lang.Runtime.class;
获取内部类
反射调用内部类的时候需要使用$ 来代替. ,如Common$Inner ,通过Class.forname("Common$Inner"); 加载
class Common {
static {
System.out.printf("CommonClass: %s\n", Common.class);
}
class Inner{
Inner(){
System.out.printf("InnerClass: %s\n", Inner.class);}}}
在编译的时候会成两个文件Common$Inner.class 和 Common.class 就像两个类一样。
铺垫完了,下面进入正题。
使用反射生成并操作对象
Class对象可以获得该类里面的:
- 方法(由
Method对象 表示),通过Method对象 执行方法; - 构造器(由
Constructor对象 表示),通过Constructor对象 来调用构造函数; - 成员变量值(由
Field对象 表示),通过Field对象 直接访问并修改对象的成员变量值;
创建对象(类实例)
需要先使用Class对象 获取指定的构造方法(Constructor对象) ,再调用Constructor对象的newInstance() 方法(作用是调用获取到的无参构造方法)来创建该Class对象对应类的实例。
Class类当中大体有两种获取构造函数的方法:
getConstructor*() 方法,只能获取公有方法。
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
getDeclaredConstructor*() 方法,可获取到类的私有构造器(包括带有其他修饰符的构造器),须设置setAccessible() 为true。
* Constructor<?>[] getDeclaredConstructors()
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
调用方法
获得某个类的实例(Class对象)之后,通过该Class对象的getMethod*() 或getDeclaredMethod*() 来获取方法。
getMethod*() 方法,获取类的所有共有方法,包括自身、从基类继承的、从接口实现的所有public方法。
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
getDeclaredMethod*() 方法,可获取类自身声明的所有方法,包含public、protected和private方法。
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
接着在获得Method对象之后,可通过该Method对象的invoke() 方法来调用其对应方法。
Object invoke(Object obj, Object... args)
obj - 被调用方法的类实例对象(静态方法则为类)
args - 用于方法调用的参数类型列表(Java存在方法重载,以此确定要调用的方法)
在调用对象的private方法时,需要先使用Methon.setAccessible(true) 设置为忽略访问权限的限制。
调用成员变量
通过Class对象的getField*() 或getDeclaredField*() 方法可以获取该类所包含的指定成员变量。
getField*() 只能访问public修饰的变量。
Field[] getFields()
Field getField(String name)
getDeclaredField*() 可以随意的访问指定对象的所有成员变量,包括private成员变量。
Field[] getDeclaredFields()
Field getDeclaredField(String name)
获取成员变量值:
Object obj = field.get(类实例对象);
修改成员变量值:
field.set(类实例对象, 修改后的值);
在操作私有变量时,field.setAccessible(true) 即可忽略访问成员变量访问权限限制。
修改被final 关键字修饰的成员变量,需先修改set方法。
Field modifiers = field.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(类实例对象, 修改后的值);
上面三个部分都提到了调用setAccessible() 方法去解决修饰符的权限问题,该方法不属于这三者的某一类,setAccessible() 属于Field、Method 和 Constructor 对象的父类AccessibleObject 。它提供了在使用反射对象时将其标记为抑制默认 Java 语言访问控制检查的能力,其override属性默认为false,可调用setAccessible() 方法改变。因此Field、Method 、Constructor 都可调用此方法。
实例——反射java.lang.Runtime命令执行
Class runtimeClazz = Class.forName("java.lang.Runtime");
Constructor constructor = runtimeClazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object runtimeInstance = constructor.newInstance();
Method runtimeMethod = runtimeClazz.getMethod("exec", String.class);
runtimeMethod.invoke(runtimeInstance, "calc.exe");
Runtime是在写命令执行payload时最常用的类,Runtime类是单例模式的。
构造方法是私有的,但给了一个静态方法getRuntime() 来获取Runtime对象,所以还可以使用如下payload
Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime, "calc.exe");
Java反射机制总结
Java反射机制是Java动态性中最为重要的体现,利用反射机制我们可以轻松的实现Java类的动态调用。Java的大部分框架都是采用了反射机制来实现的(如:Spring MVC 、ORM框架 等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。 thod.invoke(clazz); execMethod.invoke(runtime, “calc.exe”);
## Java反射机制总结
Java反射机制是Java动态性中最为重要的体现,利用反射机制我们可以轻松的实现Java类的动态调用。Java的大部分框架都是采用了反射机制来实现的(如:`Spring MVC`、`ORM框架`等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。
|