java.lang.Runtime因为有一个exec方法可以执行本地命令,所以在很多的payload中我们都能看到反射调用Runtime类来执行本地系统命令,通过学习如何反射Runtime类也能让我们理解反射的一些基础用法。
不使用反射执行本地命令代码片段:

  1. // 输出命令执行结果
  2. System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));

如上可以看到,我们可以使用一行代码完成本地命令执行操作,但是如果使用反射就会比较麻烦了,我们不得不需要间接性的调用Runtimeexec方法。
反射Runtime执行本地命令代码片段:

  1. // 获取Runtime类对象
  2. Class runtimeClass1 = Class.forName("java.lang.Runtime");
  3. // 获取构造方法
  4. Constructor constructor = runtimeClass1.getDeclaredConstructor();
  5. constructor.setAccessible(true);
  6. // 创建Runtime类示例,等价于 Runtime rt = new Runtime();
  7. Object runtimeInstance = constructor.newInstance();
  8. // 获取Runtime的exec(String cmd)方法
  9. Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
  10. // 调用exec方法,等价于 rt.exec(cmd);
  11. Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
  12. // 获取命令执行结果
  13. InputStream in = process.getInputStream();
  14. // 输出命令执行结果
  15. System.out.println(IOUtils.toString(in, "UTF-8"));

反射调用Runtime实现本地命令执行的流程如下:

  1. 反射获取Runtime类对象(Class.forName("java.lang.Runtime"))。
  2. 使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
  3. 获取Runtime类的exec(String)方法(runtimeClass1.getMethod("exec", String.class);)。
  4. 调用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))。

上面的代码每一步都写了非常清晰的注释,接下来我们将进一步深入的了解下每一步具体含义。

反射创建类实例

在Java的任何一个类都必须有一个或多个构造方法,如果代码中没有创建构造方法那么在类编译的时候会自动创建一个无参数的构造方法。
Runtime类构造方法示例代码片段:

  1. public class Runtime {
  2. /** Don't let anyone else instantiate this class */
  3. private Runtime() {}
  4. }

从上面的Runtime类代码注释我们看到它本身是不希望除了其自身的任何人去创建该类实例的,因为这是一个私有的类构造方法,所以我们没办法new一个Runtime类实例即不能使用Runtime rt = new Runtime();的方式创建Runtime对象,但示例中我们借助了反射机制,修改了方法访问权限从而间接的创建出了Runtime对象。
runtimeClass1.getDeclaredConstructorruntimeClass1.getConstructor都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法时候我们会使用前者去获取构造方法。如果构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组,如:clazz.getDeclaredConstructor(String.class, String.class)
如果我们想获取类的所有构造方法可以使用:clazz.getDeclaredConstructors来获取一个Constructor数组。
获取到Constructor以后我们可以通过constructor.newInstance()来创建类实例,同理如果有参数的情况下我们应该传入对应的参数值,如:constructor.newInstance("admin", "123456")。当我们没有访问构造方法权限时我们应该调用constructor.setAccessible(true)修改访问权限就可以成功的创建出类实例了。