Unsafe 类不是一个 ClassLoader, 但是为什么要在本篇文章提起, 其实是因为该类可以进行注入恶意类到 JVM 中.
Unsafe 类简介
sun.misc.Unsafe 类是一个提供底层、不安全的操作,比如直接内存访问、线程调度、原子操作等功能的工具类。
这个类主要被Java内部库使用,比如Java的NIO、并发包等,因为它允许绕过Java的内存管理模型,直接进行内存操作,这可能导致程序崩溃、数据损坏等严重后果。因此,它被认为是"不安全"的,并且不建议在常规的应用程序开发中使用。
Unsafe 类详解
我们可以看到图中的定义, theUnsafe
成员属性的定义在static
代码块中进行初始化了。 所以我们可以通过Unsafe.getUnsafe
来得到该对象, 但是这里有一个限制。
根据下图进行代码分析:
Unsafe
的构造方法为private
修饰符, 所以我们无法在程序中直接new Unsafe()
进行实例化生成, 否则程序将报错:
这里的话我们可以通过反射进行暴破获取该类的theUnsafe
属性, 当然也可以通过反射暴破该类的构造方法, 都可以进行获取到Unsafe
类, 代码测试如下:
public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Class<?> clazz = Class.forName("sun.misc.Unsafe");Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();declaredConstructor.setAccessible(true);Unsafe unsafe = (Unsafe) declaredConstructor.newInstance();System.out.println(unsafe); // sun.misc.Unsafe@4554617c}
}
以及:
public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {Class<?> clazz = Class.forName("sun.misc.Unsafe");Field theUnsafe = clazz.getDeclaredField("theUnsafe"); // 因为 theUnsafe 使用 static 进行修饰, 在 static 代码块中进行初始化, 所以这里无需创建 Unsafe 对象, 静态调用就可以得到.theUnsafe.setAccessible(true);Unsafe o = (Unsafe) theUnsafe.get(null);System.out.println(o); // sun.misc.Unsafe@74a14482}
}
Unsafe 类利用
我们可以通过反射得到 Unsafe 对象, 那么我们如何利用呢?
我们在Unsafe 类详解
中图中已经看到了, 该类定义了三个native
定义的方法, 这些方法都是由C/C++
底层实现的, 如下:
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
// 可以加载字节码
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3); // 可以加载字节码
public native Object allocateInstance(Class<?> var1) throws InstantiationException; // 实例化任意类
defineClass 案例
public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {Class<?> clazz = Class.forName("sun.misc.Unsafe");Field theUnsafe = clazz.getDeclaredField("theUnsafe"); // 因为 theUnsafe 使用 static 进行修饰, 在 static 代码块中进行初始化,// 所以这里无需创建 Unsafe 对象, 静态调用就可以得到.theUnsafe.setAccessible(true);Unsafe o = (Unsafe) theUnsafe.get(null);System.out.println(o); // sun.misc.Unsafe@74a14482byte[] poc = new BASE64Decoder().decodeBuffer("yv66vgAAADQAKAoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAVMQ01EOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHAB0BAApTb3VyY2VGaWxlAQAIQ01ELmphdmEMAAoACwcAIgwAIwAkAQAEY2FsYwwAJQAmAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKACcBAANDTUQBABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAgABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAAgADgAAAAwAAQAAAAUADwAQAAAACAARAAsAAQAMAAAAZgADAAEAAAAXuAACEgO2AARXpwANS7sABlkqtwAHv7EAAQAAAAkADAAFAAMADQAAABYABQAAAAsACQAOAAwADAANAA0AFgAPAA4AAAAMAAEADQAJABIAEwAAABQAAAAHAAJMBwAVCQABABYAAAACABc=");/*该 Base64 值是如下类的字节码 Base64 后的值public class CMD {static {try {Process exec = Runtime.getRuntime().exec("calc");} catch (IOException e) {throw new RuntimeException(e);}}} */Class<?> evilClazz = o.defineClass("CMD", poc, 0, poc.length, ClassLoader.getSystemClassLoader(),new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, ClassLoader.getSystemClassLoader(), null));System.out.println(evilClazz); // class CMDevilClazz.newInstance();}
}
运行完毕后, 将弹出计算器。
Java 11
开始Unsafe
类已经把defineClass
方法移除了(defineAnonymousClass
方法还在)。
defineAnonymousClass 案例
public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {Class<?> clazz = Class.forName("sun.misc.Unsafe");Field theUnsafe = clazz.getDeclaredField("theUnsafe"); // 因为 theUnsafe 使用 static 进行修饰, 在 static 代码块中进行初始化,// 所以这里无需创建 Unsafe 对象, 静态调用就可以得到.theUnsafe.setAccessible(true);Unsafe o = (Unsafe) theUnsafe.get(null);System.out.println(o); // sun.misc.Unsafe@74a14482byte[] poc = new BASE64Decoder().decodeBuffer("yv66vgAAADQAKAoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAVMQ01EOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHAB0BAApTb3VyY2VGaWxlAQAIQ01ELmphdmEMAAoACwcAIgwAIwAkAQAEY2FsYwwAJQAmAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKACcBAANDTUQBABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAgABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAAgADgAAAAwAAQAAAAUADwAQAAAACAARAAsAAQAMAAAAZgADAAEAAAAXuAACEgO2AARXpwANS7sABlkqtwAHv7EAAQAAAAkADAAFAAMADQAAABYABQAAAAsACQAOAAwADAANAA0AFgAPAA4AAAAMAAEADQAJABIAEwAAABQAAAAHAAJMBwAVCQABABYAAAACABc=");/*该 Base64 值是如下类的字节码 Base64 后的值public class CMD {static {try {Process exec = Runtime.getRuntime().exec("calc");} catch (IOException e) {throw new RuntimeException(e);}}} */Class<?> evilClazz = o.defineAnonymousClass(Class.class, poc, null);System.out.println(evilClazz); // class CMD/356573597evilClazz.newInstance();}
}
运行弹出计算器.
allocateInstance 案例
定义如下类:
public class Cat {private Cat(){}
}
测试程序:
public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {Class<?> clazz = Class.forName("sun.misc.Unsafe");Field theUnsafe = clazz.getDeclaredField("theUnsafe"); // 因为 theUnsafe 使用 static 进行修饰, 在 static 代码块中进行初始化,// 所以这里无需创建 Unsafe 对象, 静态调用就可以得到.theUnsafe.setAccessible(true);Unsafe o = (Unsafe) theUnsafe.get(null);System.out.println(o); // sun.misc.Unsafe@74a14482Cat cat = (Cat) o.allocateInstance(Cat.class);System.out.println(cat); // com.heihu577.Cat@1540e19d}
}
最终可以实例化Cat类。