0%

从Java反射机制到RCE

前言

之前Java学了个寂寞,很多东西都没有涉及到,做项目也只是写业务逻辑CRUD没涉及到很多Java特性,一边整理姿势一边了解下Java参考

本文参考了Epicccal师傅的文章

Java 反射

定义如下:

Java 反射机制是指在程序运行时 , 对于任何一个类 , 都能知道这个类的所有属性和方法 , 对于任何一个实例对象 , 都能调用该对象的任何一个属性和方法 .

Java中这种 “ 动态获取信息 “ 和 “ 动态调用属性方法 “ 的机制被称为 Java 反射机制.

实例对象可以通过反射机制获取它的类 , 类可以通过反射机制获取它的所有方法和属性 . 获取的属性可以设值 , 获取的方法可以调用 .

简单地来说就是补充了Java作为静态语言在调用方法和属性上不灵活的缺点,通过这种机制得以动态的创建对象和调用其方法属性(写到这里有个疑问"打断点的时候能访问对象属性是不是就是因为这个反射机制的存在")

Java反射的机制的功能:

  1. 在程序运行时查找一个对象所属类
  2. 在程序运行时查找任意一个类的成员变量和方法
  3. 在程序运行时构造任意一个类的对象
  4. 在程序运行时调用任意一个方法的对象

查找一个对象所属类

  • obj.getClass()
  • obj.forName(className)
  • className.class

查找一个类的方法

  • className.getMethod(functionName , [parameterTypes.class])
  • className.getMethods()
  • className.getDeclaredMethods()

构造任意一个类的对象

  • className.newInstance()这个方法已被废弃,但不影响使用

    使用这个函数的时候默认会调用无参数的构造函数

    执行前提:

    1. 类必须要有无参构造函数 .
    2. 类的构造函数不能是私有的 , 也就是不能通过 “private“ 修饰符来修饰构造函数 .
  • className.getConstructor( parameterType ).newInstance( parameterName )

     会返回所有构造方法的一个子集,即public修饰符修饰的

调用任意一个实例对象的方法

  • Method.invoke(obj , args[])

demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class reflectTest {
public String print(String name) {
return name;
}

public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException,
ClassNotFoundException, InvocationTargetException {

String name = "harry";
// Class.forName()返回一个类
Class<?> cls = Class.forName("reflectTest");
// newInstance()或者getDeclaredConstructor().newInstance()
// 返回一个实例,类的实例化即对象
// reflectTest obj = (reflectTest) cls.newInstance();
reflectTest obj = (reflectTest) cls.getDeclaredConstructor().newInstance();
// getmethod获取一个类的所有方法,invoke用于调用任意一个实例的方法
Object ret = cls.getMethod("print", String.class).invoke(obj, name);
System.out.println(ret);

}
}

RCE

基本过程:

  1. 获取命令执行的类,使用Class.forName()

  2. 查看类中所有方法,使用className.getMethods()

  3. 找到可利用的方法构造invoke,Method.invoke(obj , args[])

    obj是构造出来的类,method是想要执行的方法,args是需执行的命令

    正常情况访问是 静态方法:类名.方法名 成员函数:对象名.方法名

    invoke就相当于反过来,方法名.invoke(类名)或者方法名.invoke(对象名)

可以利用的类一般有两种:

  1. java.lang.RunTime
  2. java.lang.ProcessBuilder

无参RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class reflect2RCE {

public reflect2RCE() {
}

public static void main(String[] args)
throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException,
IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException {
// 获取java.lang.Runtime的类
Class<?> cls = Class.forName("java.lang.Runtime");
System.out.println("通过Class.forName()方法获取任意类" + cls);
// 通过getMethods,看看类中有啥可以利用的
Method[] methods = cls.getMethods();
for (Method m : methods) {
System.out.println(m);
}
// 同样地getDeclaredMethods也可以看
}

然后和刚刚一样试着invoke调用exec方法.

1
2
3
// 获取java.lang.Runtime的类
Class<?> cls = Class.forName("java.lang.Runtime");
Object obj = cls.getMethod("exec", String.class).invoke(cls.newInstance(), "ls");

报错,因为这个java.lang.Runtime中构造函数是私有的(ClassName.instance()这个函数会调自动用的无参构造函数)

绕过:

使用反射时,类初始化的时候会对类中所有的静态方法进行调用,下图是源码,所以只要完成一个runtime类的初始化即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class reflect2RCE {

public reflect2RCE() {
}

// 使用java.lang.runtime来进行反射
public void reflect_runtime()
throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException,
IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException {
// 获取java.lang.Runtime的类
Class<?> cls = Class.forName("java.lang.Runtime");
// System.out.println("通过Class.forName()方法获取任意类" + cls);
// 通过getMethods,看看类中有啥可以利用的
// Method[] methods = cls.getMethods();
// for (Method m : methods) {
// System.out.println(m);
// }
// 同样地getDeclaredMethods也可以看
// Object obj = cls.getMethod("exec", String.class).invoke(cls.newInstance(),
// args);

// 获取方法(getRuntime,exec)
Method mGetRuntime = cls.getMethod("getRuntime");
Method mExec = cls.getMethod("exec", String.class);
// 获取runtime的实例对象,无法通过无参数构造函数构造对象就绕过,通过静态方法执行
// (以 Java 反射为例 , 在类初始化时会执行 static{} 代码块中的内容)
Object obj = mGetRuntime.invoke(null);


Process p = (Process) mExec.invoke(obj, "ls");
// 读取命令执行的文件流然后打印
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
// System.out.print(obj);
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}

public static void main(String[] args)
throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException,
IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException {
reflect2RCE r = new reflect2RCE();
r.reflect_runtime();

}

}

RCE成功

如何访问私有方法

之前的调用Runtime执行系统命令没有直接调用它的那个私有方法,而是通过 java.lang.Runtime 执行系统命令时 , 由于该类的构造方法 Runtime() 是一个私有方法 , 所以我们不能调用该方法 , 只能通过 getRuntime() 静态方法来返回一个 Runtime 实例对象 , 然后再调用 exec() 方法

  • className.getDeclaredConstructor()

    会返回所有的构造方法,包括protected,private.

    访问私有类可以将此函数的返回值(Constructor <?> )附加一个参数className.setAccessible(true);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 使用java.lang.runtime来进行反射
public void reflect_runtime()
throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException,
IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException {
// 获取java.lang.Runtime的类
Class<?> cls = Class.forName("java.lang.Runtime");
Method mExec = cls.getMethod("exec", String.class);

// 执行私有函数,非返回一个runtime方法调用静态方法的形式
Constructor<?> cst = cls.getDeclaredConstructor();
//该值为真时表示反射的对象在使用时应会禁止java的语法访问检查
//即public protect private无效
cst.setAccessible(true);
Object obj = cst.newInstance();

Process p = (Process) mExec.invoke(obj, "ls");

// 读取命令执行的文件流然后打印
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
// System.out.print(obj);
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}

带参RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class reflect2RCE {

public reflect2RCE() {
}

// 使用java.lang.ProcessBuilder
public void reflect_processBuilder()
throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException,
IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException {
// 无参数命令
Class<?> cls = Class.forName("java.lang.ProcessBuilder");
// Object obj = cls.getConstructor(List.class).newInstance(Arrays.asList("ls"));
Method startCmd = cls.getMethod("start");
// 带参命令
Object obj = cls.getConstructor(List.class).newInstance(Arrays.asList("uname", "-a"));

Process p = (Process) startCmd.invoke(obj);
// 读取命令执行的文件流然后打印
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
// System.out.print(obj);
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
reflect2RCE r = new reflect2RCE();
//调用reflect_processBuilder();
r.reflect_processBuilder();

}

}

总结

通过反射来执行RCE的基本流程就是

  1. 构造存在RCE的类
  2. 构造存在RCE类中具体方法method
  3. 构造此类的实例对象obj
  4. 用实例对象obj和具体方法method使用invoke执行RCE