java代码审计自学:反射机制
0x00 简介
主要是因为自己的学习Java 代码审计中的学习思路吧,主要自己一个人学习,有点闭门造车,百度学习法,但是还是记录一下,也分享一下,也便于将来的总结和反思,如果我能终能学到什么,我也会重新梳理思路,为那些自学者提供一个好的思路,所以有了下面的系列文章java代码审计自学篇。
P牛的文章中说到:
Java安全可以从反序列化漏洞开始说起,反序列化漏洞⼜可以从反射开始说起。
0x01 Java反射机制
我之前觉得Java学起来感觉是比较死的,因为我只是站在变成的角度,php的各种动态的调用,免杀起来都方便的不行,但是发现java的提供的“反射”功能,也是可以提供⼀些动态特性,也是灵活的
所以,Java反射(Reflection
)是Java非常重要的动态特性
我们通过使用反射我们不仅可以获取到任何类的成员方法(Methods
)、成员变量(Fields
)、构造方法(Constructors
)等信息
还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。
简单的说就是,我们用对象可以通过反射获取他的类,用类可以通过拿到它的所有⽅法(包括私有),拿到的⽅法可以为所欲为??
0x02 基础知识
⼏个在反射⾥极为重要的⽅法:
1.获取class的字节码对象:
Java反射操作的是java.lang.Class
对象,所以我们需要先想办法获取到Class对象,通常我们有如下几种方式获取一个类的Class对象:
类名.class
,如:com.zeo.sec.Test.class
。如果你已经加载了某个类,那么就直接拿它的 class 属性Class.forName("com.zeo.sec.Test.class")
。如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取classLoader.loadClass("com.zeo.sec.Test.class");
obj.getClass()
如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过obj.getClass() 来获取它的类
以最常用的代码执行获取Runtime类Class对象代码片段:
1 | String className = "java.lang.Runtime"; |
2.获取名字
可以反射类名。
1 | getName()//获取全名 例如:com.test.Demo |
3.获取构造函数
1 | getConstructors()//获取所有公开的构造函数 |
获取当前类所有的成员方法:
1 | Method[] methods = clazz.getDeclaredMethods() |
获取当前类指定的成员方法:
1 | Method method = clazz.getDeclaredMethod("方法名"); |
4.实例化类对象的⽅法: newInstance()
class.newInstance() 的作用就是调用这个类的无参构造函数,如果使用 newInstance 总是不成功,这时候原因可能是:
你使用的类没有无参构造函数 (getConstructor方法解决)
你使用的类构造函数是私有的 (getDeclaredConstructor方法解决)
5.反射调用方法执⾏函数的⽅法: invoke
获取到java.lang.reflect.Method
对象以后我们可以通过Method
的invoke
方法来调用类方法。
调用类方法代码片段:
1 | method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开); |
1 | invoke 的作用是执行方法,它的第一个参数是: |
0x03 特殊情况
如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类
使用新的反射方法 getConstructor
getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载,所以必须用参数列表类型才能唯一确定一个构造函数。
获取到构造函数后,我们使用 newInstance 来执行。
比如,我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用start() 来执行命令
如果一个方法或构造方法是私有方法,我们是否能执行它呢
普通的 getMethod 、getDeclaredMethod 区别是:
getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
getDeclaredConstructor和getConstructor都可以获取到类构造方法,区别在于:
getConstructor无法获取到私有方法,所以一般在获取某个类的构造方法时候我们会使用getDeclaredConstructor去获取构造方法。如果构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组,如:clazz.getDeclaredConstructor(String.class, String.class)
。
举个例子,前文我们说过Runtime这个类的构造函数是私有的,我们需要用 Runtime.getRuntime() 来获取对象。其实现在我们也可以直接用 getDeclaredConstructor 来获取这个私有的构造方法来实例化对象,进而执行命令:
1 | // 获取Runtime类对象 |
反射调用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)
)。
参考
P牛知识星球中的java漫谈