上一篇学完了反射的基础,接下来深入学习一下。
在正常情况下,除了系统类,如果我们想拿到一个类,需要先 import 才能使用。而使用forName就不 需要,这样对于我们的攻击者来说就十分有利,我们可以加载任意类
同时,forName()还可以获取内部类
我们通过forName获得类之后用newInstance()来调用这个类的无参构造函数,但有时这个方法会失败,原因可能是:
- 你使用的类没有无参构造函数
- 你使用的类构造函数是私有的
常见的情况就是 java.lang.Runtime ,这个类在我们构造命令执行Payload的时候很常见,但 我们不能直接这样来执行命令:因为Runtime类的构造方法是私有的。1
Class clazz = Class.forName("java.lang.Runtime"); clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");
但还是有方法获取到这个类的,这涉及到单例模式。
比如,对于Web应用来说,数据库连接只需要建立一次,而不是每次用到数据库的时候再新建立一个连 接,此时作为开发者你就可以将数据库连接使用的类的构造函数设置为私有,然后编写一个静态方法来 获取:之后获取这个类的方法为getInstance。1
2
3
4
5
6
7
8public class TrainDB {
private static TrainDB instance = new TrainDB();
public static TrainDB getInstance() {
return instance;
}
private TrainDB() {
// 建立连接的代码...
}
Runtime类就是单例模式,我们只能通过 Runtime.getRuntime() 来获取到 Runtime 对 象。我们将上述Payload进行修改即可正常执行命令了:1
2Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");
Runtime.exec有6个重载,第一个重载,它只有一个参数,类型是String,所以我们使用 getMethod(“exec”, String.class) 来获取 Runtime.exec 方法。
invoke 的作用是执行方法,它的第一个参数是:
1.如果这个方法是一个普通方法,那么第一个参数是类对象
2.如果这个方法是一个静态方法,那么第一个参数是类
这也比较好理解了,我们正常执行方法是 [1].method([2], [3], [4]…) ,其实在反射里就是 method.invoke([1], [2], [3], [4]…) 。
所以我们将上述命令执行的Payload分解一下就是:
1 | Class clazz = Class.forName("java.lang.Runtime"); |