java反序列化漏洞(1)之反射机制
java反射
0x00 java反射简介
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
将类的各个部分封装为其他对象
反射是框架设计的灵魂
Java反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值
Java 反射主要提供以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
反射的好处:
- 可以在程序运行过程中,操作这些对象
- 可以解耦,提高程序的可扩展性
0x01 获取class对象
java反射操作的是java.lang.Class
对象,有一下方法获取一个类的Class对象:
类名.class
Class.forName("com.demo.classloader.TestClass")
ClassLoader.loadClass("com.demo.classLoader.TestClass")
反射调用内部类的时候需要使用$
来代替.
,如com.org.test
类有一个叫做Hello
的内部类,则在调用它的时候要写成:com.org.test$Hello
。
0x02 反射java.lang.Runtime
java.lang.Runtime
中有一个exec
方法可以执行本地命令,在很多payload中都能看见反射Runtime类来执行本地命令
不使用反射执行本地命令代码片段:
1 | // 输出命令执行结果 |
反射Runtime执行本地命令代码片段:
这里的
IOUtils
是org.apache.commons.io.IOUtils
包下的,需要使用maven导入
1 | import org.apache.commons.io.IOUtils; |
反射调用Runtime实现本地命令执行的流程如下:
- 反射获取
Runtime
类对象(Class.forName("java.lang.Runtime")
)。 - 使用
Runtime
类的Class对象获取Runtime
类的无参数构造方法(getDeclaredConstructor()
),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true)
)。 - 获取
Runtime
类的exec(String)
方法(runtimeClass1.getMethod("exec", String.class);
)。 - 调用
exec(String)
方法(runtimeMethod.invoke(runtimeInstance, cmd)
)。
0x03 反射调用类方法
Class
对象提供了一个获取某个类的所有的成员方法的方法,也可以通过方法名和方法参数类型来获取指定成员方法
获取当前类所有的成员方法:
1 | Method[] methods = clazz.getDeclaredMethods(); |
获取当前类的指定的成员方法:
1 | Method method = clazz.getDeclaredMethod("方法名"); |
getMethod
和getDeclaredMethod
都能够获取到类成员方法,区别在于getMethod
只能获取到当前类和父类的所有有权限的方法(如:public),而getDeclaredMethod
能获取到当前类的所有成员方法(不包含父类)。
反射调用方法
获取到java.lang.reflect.Method
对象以后我们可以通过Method
的invoke
方法来调用类方法
method.invoke
的第一个参数必须是类实例对象,如果调用的是static
方法那么第一个参数值可以传null,因为在java中调用静态方法是不需要有类实例的,因为可以直接类名.方法名(参数)的方式调用。method.invoke
的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型。
0x04 反射获取Runtime类执行命令
部分代码:
1 | Integer i = 1; |
getMethod(方法名, 方法类型)
invoke(某个对象实例, 传入参数)
invoke的作用是执行方法,如果这个参数是一个普通方法,那么第一个参数就是类对象;如果这个方法是一个静态方法,那么第一个参数是类
0x05 反射小问题
5.1 当一个类没有无参构造放法,也没有类似单例模式里的静态方法时,如何通过反射实例化该类?
在面对以上问题时,需要用到一个新的反射方法getConstructor
这个方法和getMethod
相似,getConstructor
接受的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须使用参数列表类型才能唯一确定一个构造函数。
在获取到构造函数后,使用newInstance
来执行
ProcessBuilder有两个构造函数:
- public ProcessBuilder(List command)
- public ProcessBuilder(String… command)
1 | Class clazz = Class.forName("java.lang.ProcessBuilder"); |
上面是使用的第一种,所以在传入的是LIst.class
,通过 getMethod("start")
获取到start
方法,然后 invoke
执行, invoke
的第一个参数就是 ProcessBuilder Object了。
对于第二种构造函数,要怎么使用反射来执行呢?
这里就又涉及到java里的可变长参数(varargs)了;当定义函数的时候不确定参数数量时,就可以使用...
这样的语法来表示这个函数的参数个数是可变的 ;对于可变参长数,java在编译的时候会编译成一个数组。
那么,对于反射来说,如果要获取的目标函数里包含可变长参数,只要认为它是数组就行了。
所以,将字符串数组的类String[].class
传给getConstructor
,就可以获取ProcessBuilder
的第二种构造参数了
1 | Class clazz = |
在调用newInstance
的时候,因为这个函数本身接受的是一个可变长参数,我们在传给ProcessBuilder
的也是一个可变长参数,二者叠加为一个二维数组
5.2 如果一个方法或构造方法是私有方法,我们是否能执行他
答案是可以,这里可以使用getDeclared
系列的反射,
getDeclaredMethod
系列的方法获取的是当前类中声明的方法, 是实在写在这个类里的,包括私有方法, 但从父类继承来得就不包含了getMethod
系列方法获取的是当前类中所有公共方法, 包括从父类中继承的方法
0x06 Java反射机制总结
java反射机制是Java动态性中最为重要的体现,利用反射机制我们可以轻松的实现Java类的动态调用。Java的大部分框架都是采用了反射机制来实现的(如:Spring MVC
、ORM
框架等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用