前言
Java反射机制可以无视类方法和变量去访问权限修饰符,并且可以调用任何类的任何方法,访问并修改成员变量值。也就是说能控制反射的类名、方法名和参数的前提下,如果我们发现了一个java反射调用的漏洞,我们就可以为所欲为了。
反射 Reflection
反射是java的特性之一,在C/C++语言中不存在反射这个概念,有了反射java可以更加灵活调控自身运行时的信息,操作类和对象的内部属性。
反射的用途非常广,在我们使用IDE工具进行开发的时候,我们输入一个对象并且想调用它的属性或者方法的时候,IDE会自动列出它的属性和方法,这些就是通过反射来实现的。再比如JavaBean和JSP之间的调用也是通过反射来实现的。最重要的就是各种通用用途的开发框架,比如Spring框架以及ORM框架,他们都是通过反射机制来实现的。
对象可以通过反射获取其他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,通过反射功能我们可以给Java这种静态语言加上动态特性。
动态特性是什么呢,phith0n
师傅提到过 “一段代码,改变其中的变量,将会导致这段代码产生功能性的变化,我称之为动态特性”
用途
获取对象类
1 2 3 4 5 6 7
| Class.forName("java.lang.Runtime") Runtime.class runtime.getClass() ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime") // 和forName类似 但是不会执行static代码
Class.forName("[D") Class.forName("[[Ljava.lang.String")
|
反射调用内部类的时候需要用$
如 com.xxx.test 内有个H的类,调用的时候就需要写成com.xxx.test$H
forName 有两个函数重载:
1 2 3
| Class.forName(className) // 等于 Class.forName(className,true,currentLoader)
|
默认情况下forName 第一个参数是类名,第二个参数表示是否初始化,第三个参数就是Classloader
是否初始化是什么意思呢
1 2 3 4 5 6 7 8 9 10 11
| public class TrainPrint { { System.out.printf("Empty block initial %s\n", this.getClass()); } static { System.out.printf("Static initial %s\n", TrainPrint.class); } public TrainPrint() { System.out.printf("Initial %s\n", this.getClass()); } }
|
比如这段代码,首先调用的是static{}
其次是 {}
,最后是构造函数。
其中static{}
就是在类初始化的时候调用的,而{}
中的代码会放在构造函数super()
的后面,但在当前构造函数内容的前面。
所以说,forName 的初始化就是告诉Java虚拟机是否执行“类初始化”。
比如我们有如下函数,name参数可控
1 2 3
| public void ref(String name)throws Exception{ Class.forName(name); }
|
我们就可以编写一个恶意类,放在static{}
中,从而执行
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Touch { static { try { Runtime runtime=Runtime.getRuntime(); String[] commands={"touch","/tmp/ss"}; Process pc= runtime.exec(commands); pc.waitFor();
}catch (Exception e){
} } }
|
获取类方法
1 2 3 4 5 6
| getDeclaredMethods() getMethods() getMethod("exec", String.class) //获取一个特定的方法,后面的对应参数类型的Class对象 只可以public修饰符 getDeclaredMethod("exec", String.class) //获取所有修饰符的方法,跟getMethod一样 getDeclaredConstructor getConstructor
|
如果构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组
1
| clazz.getDeclaredConstructor(String.class, String.class)
|
获取到Constructor
后就可以拿来创建实例,有参数需要传入
1
| constructor.newInstance("test","test")
|
反射调用方法
1
| getRuntime.invoke(runtime,"whoami")
|
获取成员变量
1 2 3 4 5 6 7
| Field fields = xxx.getDeclaredFields getFields getDeclaredField getField Object obj =field.get(String.class) //获取变量值 field.set("对象","修改值")
|
如果修改的值没有权限也可以使用
1
| field.setAccessible(true)
|
如果修改被final
关键词修饰(最终方法,无法被子类重写)
1 2 3 4 5 6 7 8 9 10 11
| // 反射获取Field类的modifiers Field modifiers = field.getClass().getDeclaredField("modifiers");
// 设置modifiers修改权限 modifiers.setAccessible(true);
// 修改成员变量的Field对象的modifiers值 ~取反 & 与操作 modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// 修改成员变量值 field.set(类实例对象, 修改后的值);
|
调用Runtime
Java任何一个类都有一个或多个构造方法,如果没有会在类编译的时候自己创建一个无参构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Class runtimeClass1 = Class.forName("java.lang.Runtime");
Constructor constructor = runtimeClass1.getDeclaredConstructor(); constructor.setAccessible(true);
Object runtimeInstance = constructor.newInstance();
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
InputStream in = process.getInputStream();
System.out.println(IOUtils.toString(in, "UTF-8"));
|