admin管理员组

文章数量:821316

Javaweb安全——Java反射

Java反射机制

Java反射(Reflection)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂。

获取Class对象

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

  1. Class.forName("ClassName");
  2. ClassLoader.loadClass("ClassName"); //实际使用时需要指定具体的加载器如ClassLoader.getSystemClassLoader(),再调用loadClass方法
  3. ClassName.class; //类已经被加载,只是获取其java.lang.Class对象
  4. 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)   //调用此方法等效于调用loadClass(name, false)
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.classCommon.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对象
Method getMethod(String name, 类<?>... parameterTypes)   //指定方法 返回Method数组

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 modifiers = field.getClass().getDeclaredField("modifiers");// 设置modifiers修改权限
modifiers.setAccessible(true);// 修改成员变量的Field对象的modifiers值
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命令执行

// 获取Runtime类对象
Class runtimeClazz = Class.forName("java.lang.Runtime");// 获取Runtime类的无参构造方法(该方法为私有方法)
Constructor constructor = runtimeClazz.getDeclaredConstructor();
//修改方法的访问权限(constructor.setAccessible(true))
constructor.setAccessible(true);// 创建Runtime类实例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();// 获取Runtime的exec(String cmd)方法
Method runtimeMethod = runtimeClazz.getMethod("exec", String.class);// 调用exec方法,等价于 rt.exec(cmd);
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 MVCORM框架等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。
thod.invoke(clazz);
execMethod.invoke(runtime, “calc.exe”);


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

本文标签: Javaweb安全Java反射