Java安全1

类加载机制

Java是一门跨平台语言,其跨平台的能力依赖于JVM,Java的.java源代码文件都必须经过编译为.class字节码文件后,由JVM加载才能运行,因为JVM在不同平台上都有实现,也就赋予了Java跨平台运行的特性。

CLassLoader的实现

ClassLoader是Java中的一个类,负责动态加载类文件(.class),Java提供三个重要的ClassLoader实现:

  • Bootstrap ClassLoader(引导类加载器):加载Java的核心类库,通常有底层C++实现,可以直接访问JVM。
  • Extension ClassLoader(拓展类加载器):加载拓展类库,通常位于JAVA_HOME/lib/ext
  • App ClassLoader(应用类加载器):加载应用程序的类和资源,是默认的类加载器。

Java中的类加载器遵循双亲委派模型(Parent Delegation Model),即当一个类加载器加载类是,会首先委托给父加载器,如果父加载器中找不到目标类,再由当前加载器尝试加载。每个类加载器都有一个父类加载器,但这里的父子关系不是通过继承实现的,通过“委托”实现的,即双亲委派模型。

这个委托是层层递归的,即最终会被最顶层的类加载器先加载,上面的类加载器的父子关系从上即Bootstrap==>Extension==>App,也就是说当App ClassLoader尝试加载类时,会委托给Extension ClassLoader,并最终委托至Bootstrap ClassLoader,由Bootstrap ClassLoader首先尝试加载。这样做的好处是可以防止类的重复加载,也能保证核心类库的安全性。

比如,Java核心中有一个String类(java.lang.String),我们又写了一个恶意的java.lang.String类来尝试覆盖,因为双亲委派模型的存在,加载恶意String类时,会被层层委派至Bootstrap ClassLoader,因为Bootstrap ClassLoader已经加载了核心类库中的java.lang.String,则不会加载自定义的java.lang.String。(不过JVM 也明确禁止了用户代码定义核心包名)

ClassLoader的核心方法如下:

  • loadClass(加载指定的Java类):首先使用findLoadedClass方法来检查待加载的类是否被加载过,若未被加载,则调用父加载器的loadClass方法。父加载器加载失败则通过findClass来加载该类。
  • findClass(查找指定的Java类):是一个抽象方法,由子类具体实现。用于根据类名加载字节码并返回一个 Class 对象。loadClass 方法最终会调用 findClass 来加载类。
  • findLoadedClass(查找JVM已加载过的类):用于判断指定的类是否已经被当前的 ClassLoader 加载。如果该类已经加载,则返回已加载的 Class 对象;如果没有加载,则返回 null
  • defineClass(定义一个Java类):用于将字节数组定义为类。通常是 ClassLoader 内部使用此方法将加载的字节码转换为 Class 对象。
  • resolveClass(链接指定的Java类):即使Class对象可用。

自定义类加载器

新建一个自定义类加载器的步骤如下:

  1. 继承ClassLoader类
  2. 重写findClass()方法
  3. 实现加载类的逻辑

例如这里实现了一个自定义的类加载器,并重写findClass方法当加载类时,若匹配到的指定的类名,则通过直接提供的类的字节码来定义该类。org.example.Test类如下,是IDEA创建新项目时自动生成的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package org.example;

//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
public class Test {
    public void Ma1n() {
        //TIP 当文本光标位于高亮显示的文本处时按 <shortcut actionId="ShowIntentionActions"/>
        // 查看 IntelliJ IDEA 建议如何修正。
        System.out.print("Hello and welcome!\n");

        for (int i = 1; i <= 5; i++) {
            //TIP 按 <shortcut actionId="Debug"/> 开始调试代码。我们已经设置了一个 <icon src="AllIcons.Debugger.Db_set_breakpoint"/> 断点
            // 但您始终可以通过按 <shortcut actionId="ToggleLineBreakpoint"/> 添加更多断点。
            System.out.println("i = " + i);
        }
    }
}

使用javac将其编译为Test.class,再用十六进制查看器获取其字节,这里我使用的imhex,可以通过右键copy直接copy为java数组。

imhex获取java数组

 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
59
60
private static final byte[] testClassBytes = new byte[]{
            (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE, 0x00, 0x00, 0x00, 0x43, 0x00, 0x33, 0x0A, 0x00, 0x02, 0x00, 0x03, 0x07,
            0x00, 0x04, 0x0C, 0x00, 0x05, 0x00, 0x06, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C,
            0x61, 0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x01, 0x00, 0x06, 0x3C, 0x69, 0x6E,
            0x69, 0x74, 0x3E, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x09, 0x00, 0x08, 0x00, 0x09, 0x07, 0x00,
            0x0A, 0x0C, 0x00, 0x0B, 0x00, 0x0C, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61,
            0x6E, 0x67, 0x2F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x01, 0x00, 0x03, 0x6F, 0x75, 0x74, 0x01,
            0x00, 0x15, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74,
            0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x3B, 0x08, 0x00, 0x0E, 0x01, 0x00, 0x13, 0x48, 0x65, 0x6C,
            0x6C, 0x6F, 0x20, 0x61, 0x6E, 0x64, 0x20, 0x77, 0x65, 0x6C, 0x63, 0x6F, 0x6D, 0x65, 0x21, 0x0A,
            0x0A, 0x00, 0x10, 0x00, 0x11, 0x07, 0x00, 0x12, 0x0C, 0x00, 0x13, 0x00, 0x14, 0x01, 0x00, 0x13,
            0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x53, 0x74, 0x72,
            0x65, 0x61, 0x6D, 0x01, 0x00, 0x05, 0x70, 0x72, 0x69, 0x6E, 0x74, 0x01, 0x00, 0x15, 0x28, 0x4C,
            0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67,
            0x3B, 0x29, 0x56, 0x12, 0x00, 0x00, 0x00, 0x16, 0x0C, 0x00, 0x17, 0x00, 0x18, 0x01, 0x00, 0x17,
            0x6D, 0x61, 0x6B, 0x65, 0x43, 0x6F, 0x6E, 0x63, 0x61, 0x74, 0x57, 0x69, 0x74, 0x68, 0x43, 0x6F,
            0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74, 0x73, 0x01, 0x00, 0x15, 0x28, 0x49, 0x29, 0x4C, 0x6A, 0x61,
            0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x0A,
            0x00, 0x10, 0x00, 0x1A, 0x0C, 0x00, 0x1B, 0x00, 0x14, 0x01, 0x00, 0x07, 0x70, 0x72, 0x69, 0x6E,
            0x74, 0x6C, 0x6E, 0x07, 0x00, 0x1D, 0x01, 0x00, 0x10, 0x6F, 0x72, 0x67, 0x2F, 0x65, 0x78, 0x61,
            0x6D, 0x70, 0x6C, 0x65, 0x2F, 0x54, 0x65, 0x73, 0x74, 0x01, 0x00, 0x04, 0x43, 0x6F, 0x64, 0x65,
            0x01, 0x00, 0x0F, 0x4C, 0x69, 0x6E, 0x65, 0x4E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x54, 0x61, 0x62,
            0x6C, 0x65, 0x01, 0x00, 0x04, 0x4D, 0x61, 0x31, 0x6E, 0x01, 0x00, 0x0D, 0x53, 0x74, 0x61, 0x63,
            0x6B, 0x4D, 0x61, 0x70, 0x54, 0x61, 0x62, 0x6C, 0x65, 0x01, 0x00, 0x0A, 0x53, 0x6F, 0x75, 0x72,
            0x63, 0x65, 0x46, 0x69, 0x6C, 0x65, 0x01, 0x00, 0x09, 0x54, 0x65, 0x73, 0x74, 0x2E, 0x6A, 0x61,
            0x76, 0x61, 0x01, 0x00, 0x10, 0x42, 0x6F, 0x6F, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x4D, 0x65,
            0x74, 0x68, 0x6F, 0x64, 0x73, 0x08, 0x00, 0x26, 0x01, 0x00, 0x05, 0x69, 0x20, 0x3D, 0x20, 0x01,
            0x0F, 0x06, 0x00, 0x28, 0x0A, 0x00, 0x29, 0x00, 0x2A, 0x07, 0x00, 0x2B, 0x0C, 0x00, 0x17, 0x00,
            0x2C, 0x01, 0x00, 0x24, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E,
            0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x43, 0x6F, 0x6E, 0x63, 0x61,
            0x74, 0x46, 0x61, 0x63, 0x74, 0x6F, 0x72, 0x79, 0x01, 0x00, (byte) 0x98, 0x28, 0x4C, 0x6A, 0x61, 0x76,
            0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65,
            0x74, 0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x73, 0x24, 0x4C, 0x6F, 0x6F, 0x6B,
            0x75, 0x70, 0x3B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74,
            0x72, 0x69, 0x6E, 0x67, 0x3B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F,
            0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65, 0x74, 0x68, 0x6F, 0x64, 0x54, 0x79, 0x70,
            0x65, 0x3B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72,
            0x69, 0x6E, 0x67, 0x3B, 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F,
            0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B, 0x29, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61,
            0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x43, 0x61, 0x6C, 0x6C, 0x53, 0x69,
            0x74, 0x65, 0x3B, 0x01, 0x00, 0x0C, 0x49, 0x6E, 0x6E, 0x65, 0x72, 0x43, 0x6C, 0x61, 0x73, 0x73,
            0x65, 0x73, 0x07, 0x00, 0x2F, 0x01, 0x00, 0x25, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E,
            0x67, 0x2F, 0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65, 0x74, 0x68, 0x6F, 0x64, 0x48,
            0x61, 0x6E, 0x64, 0x6C, 0x65, 0x73, 0x24, 0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70, 0x07, 0x00, 0x31,
            0x01, 0x00, 0x1E, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76,
            0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65, 0x74, 0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65,
            0x73, 0x01, 0x00, 0x06, 0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70, 0x00, 0x21, 0x00, 0x1C, 0x00, 0x02,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, 0x00, 0x06, 0x00, 0x01, 0x00, 0x1E,
            0x00, 0x00, 0x00, 0x1D, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x2A, (byte) 0xB7, 0x00, 0x01,
            (byte) 0xB1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
            0x05, 0x00, 0x01, 0x00, 0x20, 0x00, 0x06, 0x00, 0x01, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x59, 0x00,
            0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, (byte) 0xB2, 0x00, 0x07, 0x12, 0x0D, (byte) 0xB6, 0x00, 0x0F, 0x04,
            0x3C, 0x1B, 0x08, (byte) 0xA3, 0x00, 0x15, (byte) 0xB2, 0x00, 0x07, 0x1B, (byte) 0xBA, 0x00, 0x15, 0x00, 0x00, (byte) 0xB6,
            0x00, 0x19, (byte) 0x84, 0x01, 0x01, (byte) 0xA7, (byte) 0xFF, (byte) 0xEC, (byte) 0xB1, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1F, 0x00,
            0x00, 0x00, 0x16, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0F, 0x00,
            0x0E, 0x00, 0x1B, 0x00, 0x0B, 0x00, 0x21, 0x00, 0x10, 0x00, 0x21, 0x00, 0x00, 0x00, 0x09, 0x00,
            0x02, (byte) 0xFC, 0x00, 0x0A, 0x01, (byte) 0xFA, 0x00, 0x16, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, 0x02,
            0x00, 0x23, 0x00, 0x24, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x27, 0x00, 0x01, 0x00, 0x25,
            0x00, 0x2D, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x32, 0x00, 0x19,
    };

写一个自定义的类加载器如下:

 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
package org.example;

import java.lang.reflect.Method;

public class CustomClassBytesLoader extends ClassLoader {
    private static final String testClassName = "org.example.Test";
    private static final byte[] testClassBytes = new byte[]{(byte) 0xCA} //具体内容参见上面 

    // 重写findClass方法
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        if (name.equals("org.example.Test")) { // 匹配指定的类名
            return defineClass(name, testClassBytes, 0, testClassBytes.length); // 用上面的byte来定义该类
        }
        return super.findClass(name); // 没匹配到就交由父类加载器,不影响其他类的加载
    }

    public static void main(String[] args) {
        CustomClassBytesLoader loader = new CustomClassBytesLoader();
        try {
            Class testClass = loader.loadClass(testClassName);
            // loadClass双亲委托的三个步骤:1.检查有没有被加载2.若没有被加载,委托父加载器加载3.父加载器加载失败,调用findClass自己加载;findClass我们上面已被重写。
            
            Object testInstance = testClass.newInstance();
            // 通过反射获取该类的方法并执行
            Method method = testInstance.getClass().getMethod("Ma1n");
            // 方法是可以有参数的,有参数的话需要指定参数类型,否则会加载失败;这里的Ma1n没有参数,所以不用指定。例如函数签名:Ma2n(String p1, int p2),则getMethod("Ma2n", String.class, int.class)
            String str = (String) method.invoke(testInstance);
            System.out.println(str);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

}

当然也不必拘泥于此,通过该方法可以实现:从本地某路径加载类、请求网络实现类的远程加载。例如java.net中提供了 URLClassLoader,提供了加载远程资源的能力。可以加载本地class文件或者网络的class文件。或者匹配某类-方法进行替换当做后门。

安全领域,往往使用该机制实现自定义恶意的类加载器来加载webshell,或者自定以类字节码的native方法绕过RASP检测。

Java反射机制

Java的反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于属性能够动态获取并修改其值,对于方法,能够动态调用;这种动态获取信息以及动态调用对象方法的功能,被称为Java的反射机制。

Java反射操作的是java.lang.Class对象,获取一个类的Class对象一般有如下的方法:

  1. 类名.class,直接获取一个已加载的类的java.lang.Class,如org.example.Test.class
  2. Class.forName("org.example.Test")
  3. 实例化对象.getClass()

下面的例子演示了使用如上三种方法获取类的Class对象,并使用getName来打印其类名。

获取类的Class对象并打印其当前类名

反射获取成员变量

获取成员变量的方法位于java.lang.reflect.Field包中

  • public Field[] getFields(),获取所有public修饰的成员变量
  • public Field[] getDeclaredFields(),获取所有的成员变量,不考虑修饰符
  • public Field getField(String name) ,获取指定名称的public修饰的成员变量
  • public Field getDeclaredField(String name),获取指定的成员变量,不考虑修饰符

对于Field,常用的方法有:

  • getName(),获取名称
  • getType(),获取类型
  • get(Object obj),获取字段的值
  • getModifiers(),获取字段的修饰符
  • set(Object obj, Object value),设置字段的值
  • setAccessible(boolean flag),设置字段的可访问性(允许访问私有字段)
 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
// 获取成员变量例子
package org.example;

import java.lang.reflect.Field;

public class ReflectDemo {
    private final int a = 1;
    protected final String b  = "2";
    public final int c  = 3;

    public void hello() {
        System.out.println("Hello World");
    }

    public int getA() {
        return a;
    }

    protected String getB() {
        return b;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        // c3
        Class c3 = Class.forName("org.example.ReflectDemo");

        System.out.println(c3.getName());
        System.out.println("----------获取所有public成员变量----------");
        for (Field f : c3.getFields()) {
            System.out.print(f.getName()+" | "); // 获取变量名称
            System.out.println(f.getType()); // 获取变量类型
        }
        System.out.println("----------获取所有成员变量----------");
        for (Field f : c3.getDeclaredFields()) {
            System.out.print(f.getName()+" | "); // 获取变量名称
            System.out.println(f.getType()); // 获取变量类型
        }
        System.out.println("----------获取指定成员变量----------");
        Field fieldA = c3.getDeclaredField("a");
        fieldA.setAccessible(true); // 对于private字段,需要设置可访问
        System.out.print(fieldA.getName()+" | ");
        System.out.print(fieldA.getType()+" | ");
        ReflectDemo c3Object = new ReflectDemo(); // 如果要获取值,需要有一个实例,这里新建实例供下面获取a的值
        System.out.println(fieldA.get(c3Object));
    }
}

运行结果:

获取成员变量例子运行结果

反射获取方法

获取方法的方法位于java.lang.reflect.Method包中

  • public Method[] getMethods(),获取类的所有public修饰的方法
  • public Method[] getDeclaredMethod(),获取类所有的方法
  • public Method getMethod(String name, Class<?>... parameterTypes),获取该类声明的所有public方法;前一个参数为方法名,后面的参数列表是参数类型(都要加上.class后缀,在上一节的“自定义类加载器”部分有提到)
  • public Method getDeclaredMethod(String name, Class<?>... parameterTypes),获取该类的所有方法

对于Method,常用的方法有:

  • getName(),获取方法名称
  • getReturnType(),获取方法的返回类型
  • getParameterTypes(),获取方法的参数类型
  • public int getModifiers(),获取方法的修饰符
  • invoke(Object obj, Object… args),用于调用方法
  • setAccessible(boolean flag),设置访问权限,允许调用私有方法

也可以直接打印Method的实例,来获取其完整签名,其toString方法如下:

Method的toString方法

 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
// 获取方法例子
package org.example;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectDemo {
    private final int a = 1;
    protected final String b  = "2";
    public final int c  = 3;

    public void hello() {
        System.out.println("Hello World");
    }

    public int getA() {
        return a;
    }

    protected String getB() {
        return b;
    }

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

        // c3
        Class c3 = Class.forName("org.example.ReflectDemo");

        System.out.println(c3.getName());
        System.out.println("----------获取所有public方法----------");
        for (Method f : c3.getDeclaredMethods()) {
            System.out.print(f.getName()+" | "); // 获取方法名称
            System.out.println(f.getModifiers()); // 获取方法的修饰符
        }
        System.out.println("----------获取所有方法----------");
        for (Method f : c3.getMethods()) {
            System.out.print(f.getName()+" | "); // 获取方法名称
            System.out.println(f.getModifiers()); // 获取方法的修饰符
        }
        System.out.println("----------获取所有方法的完整签名----------");
        for (Method f : c3.getMethods()) {
            System.out.println(f); //
        }
        System.out.println("----------获取指定方法并执行----------");
        ReflectDemo demo = new ReflectDemo();
        Method methodHello = c3.getMethod("hello");
        System.out.println(methodHello); // 输出hello()的完整签名
        methodHello.invoke(demo); // 执行hello()函数
    }
}

运行结果:

获取方法例子运行结果

反射获取构造函数

获取构造函数Constructor的方法位于java.lang.reflect.Constructor包中

  • public Constructor<?>[] getConstructors(),获取所有公共的构造方法
  • public Constructor<?>[] getDeclaredConstructors(),获取所有构造方法
  • public Constructor<T> getConstructor(Class<?>... parameterTypes),获取指定的公共构造方法
  • public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes),获取指定的构造方法

对于Constructor,常用的方法有:

  • 获取名称、获取参数类型、获取修饰符,以及toString方法可以一揽子打印出来,与Method的类似不赘述了
  • public T newInstance(Object … initargs),调用构造函数创建实例
  • public void setAccessible(boolean flag),对私有构造函数设置访问权限
 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
// 获取构造函数
package org.example;

import java.lang.reflect.Constructor;

public class ReflectDemo {
    public ReflectDemo() {
        System.out.println("无参构造函数");
    }
    public ReflectDemo(String str) {
        System.out.println("有参构造函数: " + str );
    }
    private ReflectDemo(String str, String str2) {
        System.out.println("私有有参构造函数: " + str + " " + str2);
    }

    public static void main(String[] args) {

        try {
            // class3
            Class class3 = Class.forName("org.example.ReflectDemo");
            System.out.println(class3.getName());

            System.out.println("----------获取所有public构造函数----------");
            Constructor[] constructors1 = class3.getConstructors();
            for (Constructor c : constructors1) {
                System.out.println(c);
            }
            System.out.println("----------获取所有构造函数----------");
            Constructor[] constructors2 = class3.getDeclaredConstructors();
            for (Constructor c : constructors2) {
                System.out.println(c);
            }
            System.out.println("----------获取指定构造函数----------");
            Constructor c1 = class3.getConstructor(); // 获取指定的public构造函数
            Constructor c2 = class3.getDeclaredConstructor(String.class, String.class); // 获取指定的构造函数,无视修饰符
            System.out.println(c1);
            System.out.println(c2);
            System.out.println("----------调用构造函数创建实例----------");
            c1.newInstance(); // 调用无参构造函数
            c2.setAccessible(true); // c2是私有的,需要首先绕过访问限制
            c2.newInstance("参数1", "参数2"); // 调用有参构造函数

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

获取构造函数例子运行结果

既然反射可以获取类中的所有方法,在有实例的情况下可以调用某函数;反射又可以获取构造函数同时新建实例,等于如果某个类中存在危险的函数,攻击者通过反射就可以直接调用了。下面尝试访问java.lang.Runtime.exec()

尝试通过反射执行命令

首先获取了java.lang.Runtime中的所有构造函数,结果发现只有一个:private java.lang.Runtime(),通过setAccessible(true)获取其权限。获取其中的所有方法,发现exec都是public的,且有6个重载。接下来就容易了,只需要设置该私有构造函数的访问权限,接着通过getMethod("exec", String.class)获取exec函数的一个重载,调用invoke并传入构造函数.newInstance()作为实例即可。

例子

理论很美好,but失败了,报错如下:

1
Unable to make private java.lang.Runtime() accessible: module java.base does not "opens java.lang" to unnamed module @34c45dca

报错截图

查了下,这个错误是因为模块化系统(Java Platform Module System, JPMS)的限制导致的,从Java 9开始引入了JPMS(我这里使用的是java23),默认情况下对一些包的反射访问做了限制,java.lang.Runtime所在的模块java.base不提供对未命名模块(即我写的这个代码)的访问权限,因此没法直接通过反射访问Runtime的私有构造函数。

但是也有解决方案,不过对于恶意利用来说已经失去了意义:添加JVM参数,--add-opens java.base/java.lang=ALL-UNNAMED,如下:

添加JVM参数

成功执行

参考链接

https://xz.aliyun.com/t/9002

https://www.javasec.org/javase/ClassLoader/

https://xz.aliyun.com/t/9117

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计