sun.misc.Unsafe

    Unsafe是Java内部API,外部是禁止调用的,在编译Java类时如果检测到引用了Unsafe类也会有禁止使用的警告:Unsafe是内部专用 API, 可能会在未来发行版中删除

    sun.misc.Unsafe代码片段:

    由上代码片段可以看到,Unsafe类是一个不能被继承的类且不能直接通过new的方式创建Unsafe类实例,如果通过getUnsafe方法获取Unsafe实例还会检查类加载器,默认只允许Bootstrap Classloader调用。

    既然无法直接通过Unsafe.getUnsafe()的方式调用,那么可以使用反射的方式去获取Unsafe类实例。

    反射获取Unsafe类实例代码片段:

    1. // 反射获取Unsafe的theUnsafe成员变量
    2. Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
    3. // 反射获取theUnsafe成员变量值
    4. Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

    当然我们也可以用反射创建Unsafe类实例的方式去获取Unsafe对象:

    假设我们有一个叫com.anbai.sec.unsafe.UnSafeTest的类,因为某种原因我们不能直接通过反射的方式去创建UnSafeTest类实例,那么这个时候使用UnsafeallocateInstance方法就可以绕过这个限制了。

    UnSafeTest代码片段:

    1. public class UnSafeTest {
    2. // 假设RASP在这个构造方法中插入了Hook代码,我们可以利用Unsafe来创建类实例
    3. System.out.println("init...");
    4. }
    5. }

    使用Unsafe创建UnSafeTest对象:

    Google的GSON库在JSON反序列化的时候就使用这个方式来创建类实例,在渗透测试中也会经常遇到这样的限制,比如RASP限制了java.io.FileInputStream类的构造方法导致我们无法读文件或者限制了UNIXProcess/ProcessImpl类的构造方法导致我们无法执行本地命令等。

    ClassLoader章节我们讲了通过类的defineClass0/1/2方法我们可以直接向JVM中注册一个类,如果ClassLoader被限制的情况下我们还可以使用UnsafedefineClass方法来实现同样的功能。

    Unsafe提供了一个通过传入类名、类字节码的方式就可以定义类的defineClass方法:

    public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

    使用Unsafe创建TestHelloWorld对象:

    1. // 使用Unsafe向JVM中注册com.anbai.sec.classloader.TestHelloWorld类

    或调用需要传入类加载器和保护域的方法:

    Unsafe还可以通过defineAnonymousClass方法创建内部类,这里不再多做测试。

    注意:

    这个实例仅适用于Java 8以前的版本如果在Java 8中应该使用应该调用需要传类加载器和保护域的那个方法。Java 11开始Unsafe类已经把defineClass方法移除了(defineAnonymousClass方法还在),虽然可以使用java.lang.invoke.MethodHandles.Lookup.defineClass来代替,但是MethodHandles只是间接的调用了ClassLoaderdefineClass,所以一切也就回到了。