sun.misc.Unsafe
sun.misc.Unsafe是Java底層API(僅限Java內部使用,反射可調用)提供的一個神奇的Java類,Unsafe提供了非常底層的內存、CAS、線程調度、類、對象等操作、Unsafe正如它的名字一樣它提供的幾乎所有的方法都是不安全的,本節只講解如何使用Unsafe定義Java類、創建類實例。
如何獲取Unsafe對象
Unsafe是Java內部API,外部是禁止調用的,在編譯Java類時如果檢測到引用了Unsafe類也會有禁止使用的警告:Unsafe是內部專用 API, 可能會在未來發行版中刪除。
sun.misc.Unsafe代碼片段:
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
public final class Unsafe {
private static final Unsafe theUnsafe;
static {
theUnsafe = new Unsafe();
省去其他代碼......
}
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (var0.getClassLoader() != null) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
省去其他代碼......
}
由上代碼片段可以看到,Unsafe類是一個不能被繼承的類且不能直接通過new的方式創建Unsafe類實例,如果通過getUnsafe方法獲取Unsafe實例還會檢查類加載器,默認只允許Bootstrap Classloader調用。
既然無法直接通過Unsafe.getUnsafe()的方式調用,那么可以使用反射的方式去獲取Unsafe類實例。
反射獲取Unsafe類實例代碼片段:
// 反射獲取Unsafe的theUnsafe成員變量
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
// 反射設置theUnsafe訪問權限
theUnsafeField.setAccessible(true);
// 反射獲取theUnsafe成員變量值
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
當然我們也可以用反射創建Unsafe類實例的方式去獲取Unsafe對象:
// 獲取Unsafe無參構造方法
Constructor constructor = Unsafe.class.getDeclaredConstructor();
// 修改構造方法訪問權限
constructor.setAccessible(true);
// 反射創建Unsafe類實例,等價于 Unsafe unsafe1 = new Unsafe();
Unsafe unsafe1 = (Unsafe) constructor.newInstance();
獲取到了Unsafe對象我們就可以調用內部的方法了。
allocateInstance無視構造方法創建類實例
假設我們有一個叫com.anbai.sec.unsafe.UnSafeTest的類,因為某種原因我們不能直接通過反射的方式去創建UnSafeTest類實例,那么這個時候使用Unsafe的allocateInstance方法就可以繞過這個限制了。
UnSafeTest代碼片段:
public class UnSafeTest {
private UnSafeTest() {
// 假設RASP在這個構造方法中插入了Hook代碼,我們可以利用Unsafe來創建類實例
System.out.println("init...");
}
}
使用Unsafe創建UnSafeTest對象:
// 使用Unsafe創建UnSafeTest類實例
UnSafeTest test = (UnSafeTest) unsafe1.allocateInstance(UnSafeTest.class);
Google的GSON庫在JSON反序列化的時候就使用這個方式來創建類實例,在滲透測試中也會經常遇到這樣的限制,比如RASP限制了java.io.FileInputStream類的構造方法導致我們無法讀文件或者限制了UNIXProcess/ProcessImpl類的構造方法導致我們無法執行本地命令等。
defineClass直接調用JVM創建類對象
ClassLoader章節我們講了通過ClassLoader類的defineClass0/1/2方法我們可以直接向JVM中注冊一個類,如果ClassLoader被限制的情況下我們還可以使用Unsafe的defineClass方法來實現同樣的功能。
Unsafe提供了一個通過傳入類名、類字節碼的方式就可以定義類的defineClass方法:
public native Class defineClass(String var1, byte[] var2, int var3, int var4);
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
使用Unsafe創建TestHelloWorld對象:
// 使用Unsafe向JVM中注冊com.anbai.sec.classloader.TestHelloWorld類
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
或調用需要傳入類加載器和保護域的方法:
// 獲取系統的類加載器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
// 創建默認的保護域
ProtectionDomain domain = new ProtectionDomain(
new CodeSource(null, (Certificate[]) null), null, classLoader, null
);
// 使用Unsafe向JVM中注冊com.anbai.sec.classloader.TestHelloWorld類
Class helloWorldClass = unsafe1.defineClass(
TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length, classLoader, domain
);
Unsafe還可以通過defineAnonymousClass方法創建內部類,這里不再多做測試。
注意:
這個實例僅適用于Java 8以前的版本如果在Java 8中應該使用應該調用需要傳類加載器和保護域的那個方法。Java 11開始Unsafe類已經把defineClass方法移除了(defineAnonymousClass方法還在),雖然可以使用java.lang.invoke.MethodHandles.Lookup.defineClass來代替,但是MethodHandles只是間接的調用了ClassLoader的defineClass,所以一切也就回到了ClassLoader。
Java Web安全
推薦文章: