java反射机制的定义
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法称为java语言的反射机制
反射是大多数语言里都必不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),总之通过"反射",我们可以将Java这种静态语言附加上动态特性
反射里的重要方法
获取类的方法:forName
获取实例化对象的方法:newInstance
获取函数的方法:getMethod
执行函数的方法:invoke
获取class对象的方法
1.类的.class属性(需要导入类的包)
Class c1 = Demo.class;
2.实例化对象的getClass方法(需要本身创建一个对象)
Demo demo = new Demo();
Class c2 = demo.getClass();
3.动态加载类
Class c3 =Class.forName('com.company.Demo');
forname有两个重载
Class.forName(className)
// 等于
Class.forName(className,true,currentLoader)
默认情况下,forName
的第一个参数是类名;第二个参数表示是否初始化(类的初始化);第三个参数就是ClassLoader
ClassLoader
就是一个"加载器",告诉JVM如何去加载这个类。Java默认的ClassLoader
就是根据类名来加载类,这个类名就是完整路径。
如java.lang.Runtime
获取成员变量
Field getField(String name) 获取指定名称的 public修饰的成员变量
Field getDeclaredField(String name) 获取指定的成员变量
Field[] getFields() :获取所有public修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量(包括私有)
获取成员方法
Method getMethod(String name,Class<?>... parameterTypes) //返回该类所声明的public方法
Method getDeclaredMethod(String name,Class<?>... parameterTypes) //返回该类所声明的所有方法
//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型
Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法
Method[] getDeclaredMethods() // 获取该类中的所有方法
获取构造函数
Constructor<?>[] getConstructors() 返回所有public构造函数
Constructor<?>[] getDeclaredConstructors() 返回所有构造函数
Constructor<> getConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的public构造函数
Constructor<> getDeclaredConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的所有构造函数
利用反射创建类对象
Person类
import java.io.Serializable;
public class Person implements Serializable {
public String name;
public int age;
public Person(){}
public Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name=" + '\'' + name + '\'' +
", age=" + age +
'}';
}
}
无参构造:
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Person person = new Person();
Class c = Class.forName("Person");
Person p =(Person) c.newInstance();
System.out.println(p);
}
}
带参数的构造:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class c = Class.forName("Person");
Constructor personconstructor = c.getConstructor(String.class,int.class );
Person p =(Person) personconstructor.newInstance("abc",12);
System.out.println(p);
}
}
在正常情况下,除了系统类,如果我们想拿到一个类,需要先import才能使用。而使用forName
就不需要,这对于攻击者来说就非常有利,可以加载任意类
另外,我们经常在一些源码里看到,类名的部分包含$
符号,$
的作用是查找内部类
Java的普通类c1中支持编写内部类c2,而在编译的时候,会生成两个文件:c1.class
和c1$c2.class
,我们可以把他们看作两个无关的类,通过class.forName("c1$c2")
即可加载这个内部类。
获得类以后,我们可以继续用反射来获取这个类中的属性,方法,也可以实例化这个类并调用方法
class.newInstance()
的作用就是调用这个类的无参构造函数,但是有时候在写漏洞利用方法的时候,会发现使用newInstance
总是不成功,原因可能是:1.使用的类没有无参构造函数;2.使用的类的构造函数是私有的
最常见的情况是java.lang.Runtime
,这个类在我们构造payload时很常见,但我们不能直接这样来执行命令
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.newInstance(),"id");
原因是Runtime
类的构造方法是私有的,因为Runtime类是单例模式,我们只能通过Runtime.getRuntime()
来获取到Runtime对象。
Class clazz = class.forName("java.lang.Runtime");
clazz.getMethod("exec","String.class").invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");
分解一下就是
Class clazz = class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec",String.class);
Method getRuntime = clazz.getMethod("getRuntime");
Object runtime = getRuntime.invoke(clazz);
execMethod.invoke(runtime,"calc.exe");
和getMethod类似,getConstructor接受的参数是构造函数列表类型,因为构造函数也支持重载,所以必须用参数列表类型才能唯一确定一个构造函数,获取到构造函数之后,我们使用newInstance来执行
我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用start()来执行命令
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
我们之前的payload也可以改成
Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec",String.class).invoke(m.newInstance(),"calc.exe");