Java反射

什么是反射机制?对于没接触过java这门语言的人来讲,这确实属于一个新的名词。(鄙人大一学过java也没接触过这个名词orz)这里引用网络上普遍存在解释:

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

看解释不难理解,反射机制能够让我们能够获取任意一个类的属性和方法,那么无疑就提高了java代码的独立性,使得程序更灵活等。那么既然有这些特点,就让我们看看反射机制如何使用,以及如何应用到java反序列化中,下面用一个demo来看看:

java-learn2-1

java-learn2-2

从代码中可以看出来,反射获取实例化对象和平常获取类的对象有点不同,这里一般分为三步来获取对象:

1.获取类对象,加载要被实例化的类,一般有三种方法

  • Class.forName("reflection.ClassTest")
  • ClassTest.class
  • new ClassTest().getClass()

一般情况下,使用第一种方法,另外两种方法需要在相同路径下,不同路径加载会导致冲突。

2.获取类对象的构造器对象,这里存在两个函数获取构造方法

方法 说明
getConstructor(Class…<?> parameterTypes) 获得该类中与参数类型匹配的公有构造方法
getConstructors() 获得该类的所有公有构造方法
getDeclaredConstructor(Class…<?> parameterTypes) 获得该类中与参数类型匹配的构造方法(主要私有)
getDeclaredConstructors() 获得该类所有构造方法

3.通过构造器获取需要被实例化类的对象

1
constructor.newInstance()

这三步结束,我们就通过反射获取到类的对象了,那么接下来我们就可以通过反射,对类的方法和属性进行访问

利用反射访问类的方法

这里java也提供了一下两种函数,来获取对象的方法

方法 说明
getMethod(String name, Class…<?> parameterTypes) 获得该类某个公有的方法
getMethods() 获得该类所有公有的方法
getDeclaredMethod(String name, Class…<?> parameterTypes) 获得该类某个方法(主要私有)
getDeclaredMethods() 获得该类所有方法

使用方法:

1
2
Method method = cls.getMethod("setName",String.class);
method.invoke(instance,"monitor");//这里调用instance对象的setName方法,参数为monitor

当然,我们也可以直接调用对象调用函数的方法instace.setName("monitor"),但是一般只能获取公有方法,这里只是演示如何通过反射机制访问类对象,同时反射机制还可以访问私有方法getDeclaredMethod,这是直接调用做不到的地方。

利用反射访问类的属性

同样java也提供两个函数

方法 说明
getField(String name) 获得某个公有的属性对象
getFields() 获得所有公有的属性对象
getDeclaredField(String name) 获得某个属性对
getDeclaredFields() 获得所有属性对象

使用方法

1
2
3
4
5
6
Field field = cls.getField("name");//获取属性名:name
field.set(instance,"monitor")//修改instance对象的name属性值为monitor
//访问私有属性
Field field1 = cls.getDeclaredField("name");
field1.setAccessible(true);
field1.set(instance,"monitor");

这里需要注意的是如何是访问私有对象时,要先使用setAccessible将访问设置为true,否在会导致无法访问,前面的构造器、方法的获取也是同理。

利用反射执行命令

从上面的学习,我们已经知道反射的基本使用方法,既然反射可以访问任意类的方法和属性,那么我们是不是可以利用这一特性来执行命令呢?说干就干,这里以代码来演示:

1
2
3
4
5
6
7
8
Runtime.getRuntime().exec("calc");//java常见的执行命令的方法
//利用反射执行命令
Class clas = Class.forName("java.lang.Runtime");
Constructor constructor1 = clas.getDeclaredConstructor();
constructor1.setAccessible(true);
Object obj = constructor1.newInstance();
Method method2 = clas.getMethod("exec", String.class);
method2.invoke(obj,"calc");

java-learn2-3

上面的代码,可能会疑惑,为什么常用方法里的getRuntime没在反射里看到使用呢。因为这里我方便学习,先按照上面的步骤来执行命令。其实在Runtime这个类里面,因为它的构造方法是私有的,所以一般情况下我们无法访问,但因为getRuntime方法会返回一个Runtime的对象,所以一般我们可以通过Runtime.getRuntime.exec来执行命令,下面以另一种方法来演示执行命令:

1
2
3
Class clas = Class.forName("java.lang.Runtime");
Object obj = clas.getMethod("getRuntime").invoke(null,null);
clas.getMethod("exec", String.class).invoke(obj,"calc");

因为getRuntime方法会返回一个对象,所以这里我们就不需要构造器来获取对象了,直接获取方法得到对象,然后执行命令。同时,这里附上利用另一个类ProcessBuilder来执行命令的代码

1
2
3
4
5
6
7
8
9
10
11
//ProcessBuilder 反射执行命令一
Class cls = Class.forName("java.lang.ProcessBuilder");
String[][] cmd = new String[][]{{"calc"}};
Constructor constructor = cls.getConstructor(String[].class);
Object obj = constructor.newInstance(cmd);
cls.getMethod("start").invoke(obj,null);
//ProcessBuilder 反射执行命令二
Class cls = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = cls.getDeclaredConstructor(List.class);
Object obj = constructor.newInstance(Arrays.asList("calc"));
cls.getMethod("start").invoke(obj,null);

到这里其实已经可以知道如何使用反射机制了,那么可能会有疑问,为什么明明一行代码就可以执行命令,偏偏要花费这么多行代码。

其实看标题就知道跟反序列化有关,这只是学反序列化前的基础学习。我们都知道,序列化后的数据保存类名、属性名,那放到上面一行代码中,序列化后就是Object:Runtime,command:calc,经过反序列化之后java根本无法识别,无法成功执行命令。但我们可以利用反射机制,构造利用链进行一层层的调用,最后得到我们想要的东西,成功执行命令。

除此之外,我们还能将反射机制完美的运用到模板注入漏洞中去,假如存在一个这样的场景:某个输入点可以进行模板注入,但是后端过滤了java.lang.Runtimejava.lang.ProcessBuilder,那我们完全可以利用反射机制绕过后端,成功进行命令执行。

总结

通过学习,我们已经大致了解了什么是反射,如何使用反射机制,那么在下一个文章,我会通过分析漏洞,来进一步学习在反序列化中利用反射,以及加深对反序列化漏洞的理解。

reference:

https://xz.aliyun.com/t/6787#toc-6

https://blog.csdn.net/sun1318578251/article/details/105817102