前言
这是正式开始学习Java反序列化漏洞的第一篇,而Java反射机制是熟知Java反序列化漏洞的第一步,此系列笔记是为了自己能更好理解反序列化漏洞,也希望通过学习,自己能深层了解漏洞成因、学会利用、自己编写、改编利用工具。
Java Reflection
什么是Java反射机制
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
通俗的来说,就是我们可以通过Java的反射机制来获取到任意一个Class、变量、method、instance等等,而我们动态地任意获取Class,正有利于我们实现反序列化漏洞的利用。
静态语言和动态特性
简单来说,动态语言可以改变一个变量的类型 - 你不用提前定义某个变量的类型,比如Python
和PHP
,这些语言会在运行时自动探针你的变量类型,而你也可以在代码中随时对这些变量类型进行改变。
而静态语言例如Java
,C/C++
,C#
就不一样,我们必须事先指定变量类型是String
,int
,还是double
?
而在Java
中,有一个反射机制,它可以为我们提供一些动态特性 - 即使Java
是一门静态语言。
Java的反射机制可以让我们做到如下:
- 在程序运行时,查找到一个
Object
所属的Class
- 在程序运行时,能找到任意一个Class的
variable
和method
- 在程序运行时,可以构造任意一个
Class
的instance(Object)
- 在程序运行时,可以调用任何一个
Object
的方法
Class类
先来看一张图:
Class类: 保存类信息的类
每一个新的class在创建的时候,都会new一个新的Class类。
比如,我们在创建一个String类,这个String类在创建的同时,会有一个Class cls=new Class(String)被创建,专门用来保存这个String Class。
再举个例子,比如我们在创建一个Person类,这个Person类在创建的时候,会有一个Class cls = new Class(Person)这样的instance被创建。但Class类和这两个类并非继承关系!!
所以,我们需要区分的是Class类是一个类,就像Person和String类一样,都是类,Person和String类在生成的时候,都会先生成Class类的instance(对象)。
在Class类中,我们含有各种方法函数,其中Class类的构造方法是private,Class类还拥有getMethod,invoke
这样的方法。
从图中可以看出:我们可以通过一个【person instance】.getClass()获取到person的Class类对象
通过【person的Class类对象】.class获取到person instance.
利用反射机制获取Class及Class静态初始化
我们知道有三种方法可以获取到一个Class对象
obj.getClass();
- 当我们知道obj instance对象时可以使用这个方法
Class.forName("Class的名字");
Class.forName( String className , Boolean initialize , ClassLoader loader );
此处
Class.forName("Class的名字");
等同于Class.forName( "Class的名字" , true , currentLoader );
String className
: 类名Boolean initialize
: 是否进行类初始化ClassLoader loader
: 加载器( 告诉 Java 虚拟机如何加载获取的类 , Java 默认根据类名( 即类的绝对路径 , 例如java.lang.Runtime()
)来加载类 )
Class
在初始化的时候,会自动执行static{}
中的代码,如果我们能够控制一个Class
,并向其中添加含有恶意代码的静态代码块,当Class
被初始化的时候就会执行恶意代码。当我们知道Class名字时可以使用这个方法
className.class
- 当我们已经加载过某个Class,可以用这个方法
其中,Class.forName("Class的名字");
是最为常用的一种方式。
1 | Class<?> person = Class.forName("Person"); |
利用反射机制获取method
我们在通过上面的代码获取到class
后,可以获取到该Class
的method
。
需要注意的是,我们依然有两种不同的方法来获取到method
。
1 | //第一种方式: |
构造instance
获取到了Class
,我们又该如何获取到一个新的object呢?
假设在Person Class中,我们有三个构造函数:
1 | public Person(){//无参构造 |
当我们依次获取时
1 | person.newInstance();//在创建新的instance时,class的constructor会被执行,而此处默认执行无参数constructor |
可得如下结果
invoke方法
当我们构造出来了一个新的instance(Object)
且得到需要的method
后,我们该如何call
这个Class
的方法呢?
1 | Object o = person.newInstance();//得到object |
补充
java.lang.Runtime命令执行及访问private的方法
我们在反序列化漏洞中会遇到的java.lang.Runtime
是执行命令的最常见的方式。
以下是java执行命令的语句
1 | Runtime.getRuntime().exec("ipconfig"); |
那如果我们要用java
的反射机制来执行这段代码应该如何操作呢?
于是我写下了这段代码:
1 | Class<?> runTimeClass = Class.forName("java.lang.Runtime");//先找到Runtime这个Class的Class类 |
但是奇怪的事情发生了:
代码提示19行出错 - 我们不能访问private
属性的成员。难道是constructor
有问题吗?
我们尝试进入Runtime Class
中看一下:
果然,该构造方法是private
的,加上前面所提到的,className.newInstance()
是直接使用无参构造,所以我们不能直接创建这个instance
。
如何解决呢?我们有两个方法
getRuntime()
其实之前我们就有写到执行语句是
Runtime.getRuntime().exec("ipconfig");
此处我们就没有new一个新的instance
,反而是直接用getter
来获取到Runtime
。所以我们在此处可以这样写:
1
2
3
4
5Class<?> runTimeClass = Class.forName("java.lang.Runtime");//先找到Runtime这个Class
Method exec = runTimeClass.getDeclaredMethod("exec", String.class);//找到exec这个method
Method getRuntime = runTimeClass.getDeclaredMethod("getRuntime");//找到getRuntime()这个method
Object o1=getRuntime.invoke(null);//call getRuntime()来获取到Runtime实例
exec.invoke(o1,"ipconfig");//执行方法setAccessible() - 访问private方法
Java
会对private
的方法进行禁止访问的操作,而setAccessible
可以让我们禁止或允许Java
语言的访问检查,当我们设置setAccessible(true)
时,我们便可以访问private
方法。1
2
3
4
5
6Class<?> runTimeClass = Class.forName("java.lang.Runtime");//先找到Runtime这个Class
Method exec = runTimeClass.getDeclaredMethod("exec", String.class);//找到exec这个method
Constructor<?> declaredConstructor = runTimeClass.getDeclaredConstructor();//不带参数的getConstructor是会获取到无参构造方法的,但因为Runtime的Constructor是private的,所以我们需要使用Declared
declaredConstructor.setAccessible(true);//禁止java语言访问检查,让我们可以访问这个私有的constructor
Object o1 = declaredConstructor.newInstance();//通过该constructor来创建新的instance
exec.invoke(o1,"ipconfig");//完成命令执行
java.lang.ProcessBuilder命令执行
知道我们是通过exec()
执行代码后,我们接下来想要弄清楚,exec()
是怎么工作的。在尝试跟随exec()方法后,我们最终来到了如下地方:
可见我们创建了一个ProcessBuilder
的instance
,同时向其中传入了我们需要执行的命令cmdArray
,cmdArray
也是以String[]
的类型传入的。
进入Processbuilder
可以看到它有两个constructor
,一个是传入有参数的(List<String>类型
),一个是传入无参数的(String[]类型
),注意,这里String...
等价于String[]
再看start()
方法
传入的command
又会被转化为Array
,最后开始创建子程序执行等等(就不深入探究了)
综上所述,我们只需要执行new ProcessBuilder.start()
即可完成命令执行
于是我们写出如下代码:
1 | Class<?> processBuilderClass = Class.forName("java.lang.ProcessBuilder");//获取到ProcessBuilder这个class |
Conclusion
Java反射机制是Java这个静态语言中的动态特性,它让我们能够获取到、执行任意类的方法、变量,也能创建任意类的对象。
Class
被初始化时会自动执行static{}
中的内容,此处可以被利用。Declared
关键字让我们可以获取处继承类以外的所有方法。setAccessible(true)
可以访问private方法,不被java
语言进行访问检测。我们可以通过Java反射机制利用Runtime和ProcessBuilder执行命令