Java Security(I) Reflection
Java安全可以从反序列化漏洞开始说起,反序列化漏洞⼜可以从反射开始说起。 ——phith0n
简介
对于java安全而言,反射是一个很重要的基础知识。我们可以通过对对象的反射来获得某一个Class,也可以通过对Class的反射获得他的所有方法(包括私有)。
在一些只有难以利用的基本类型对象(String, Char, Integer, ...)的情况下,利用反射获取到某些可以加以利用的Class 往往是一个不错的利用手段。
Exploitaion
反射中常用的方法
-
forName获取类- 参数需要是目标Class的全名
-
newInstance实例化一个对应Class的对象- 在目标类拥有无参构造函数的时候不需要参数即可使用
-
getMethod获取对应Class的某个方法 -
invoke执行方法- 一般来说至少会有一个参数,是使用这个方法的对象,后续参数是对于方法需要的参数
利用反射获取Class
- 如果上文存在对应Class的对象
obj,那么直接使用obj.getClass()方法即可。 - 通常来说不会存在对应的对象,所以需要用到
Class.forName("target-class-name")来获取到目标类
(其实如果已经加载了某个类,比如说Test,是可以直接使用Test.class来拿到这个类的,不过此方法不属于反射就是了。而且通过此方法来获取对某个类的引用的时候,是不会触发类加载器(ClassLoader)对他的初始化操作的,而通过forName来引用时则会默认触发,这事实上和forName方法的一个重载有关)
只是说的话比较枯燥,下面来看几个demo理解一下吧:
demo1-使用obj.getClass()
reflection类的内容如下(后续demo也会基于这个类来示范):
public class reflection {
public void hello(){
System.out.println("hello!");
}
private void secret(){
System.out.println("You find me!!");
}
}
现在我们要用反射获取到他的类,并使用他的hello方法。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
reflection a = new reflection();
a.hello();
Class ref = a.getClass();
ref.getMethod("hello").invoke(a);
}
}
由于hello方法是公有的,所以这里使用了两种方法来调用它。
demo2-使用forName()
在上下文没有reflection类的时候也能够调用他的方法。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class ref = Class.forName("reflection");
ref.getMethod("hello").invoke(ref.newInstance());
}
}
demo3-对私有方法的反射
在reflection类中还有一个私有方法secret(),我们无法使用getMethod来获取到他。
此时就可以使用getDeclaredMethod方法强行获取,然后再用setAccessible(true)赋予执行权限。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class ref = Class.forName("reflection");
Method secr = ref.getDeclaredMethod("secret");
secr.setAccessible(true);
secr.invoke(ref.newInstance());
}
}
(我这里使用的是jdk 8u341,此方法在高版本可能并不适用)
浅谈利用反射的RCE思路
使用forName反射java.lang.Runtime类
在java.lang.Runtime类中有exec方法,可以直接使用一些系统命令。
不过要注意的是java.lang.Runtime类在设计上使用的是单例模式,我们想使用invoke的时候还需要传一个该类型的对象进去,此时newInstance方法是无法使用的。所以还需要先反射得到getRuntime这个方法来获取一个对象。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class runtime = Class.forName("java.lang.Runtime");
Method getruntime = runtime.getMethod("getRuntime");
Object obj = getruntime.invoke(runtime);
runtime.getMethod("exec", String.class).invoke(obj, "calc.exe");
}
}
运行成功将弹出计算器。

无默认无参构造函数下的利用
java.lang.Runtime类的情况也是无法使用newInstance的,此时我们可以用 getConstructor来反射。
和getMethod类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载,
所以必须用参数列表类型才能唯一确定一个构造函数。
此外,java.lang.ProcessBuilder也是一种常用于RCE的函数,这里用它做一个示范
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class pb = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)pb.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
}
}
