java安全基础-Unsafe
java安全基础-Unsafe
全限定名为sun.misc.Unsafe,是Java底层的API提供的一个类,它提供非常底层的内存、CAS、线程程度、类、对象等操作A,是一个可以直接操作内存,不用构造器、不需要任何访问权限检查就可以创建对象的类
sun.mics.Unsafe
Unsafe源码如下:
public final class Unsafe {
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 检查调用类的加载器是不是Bootstrap,也就是null
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
上述代码可以看出,Unsafe唯一的构造器也是private,没法通过new实例化
虽然getUnsafe()是一个public方法,但是它会检查调用getUsafe()类的加载器是不是Bootstrap类加载器,但是我们定义类的默认加载器是AppClassLoader,所以会直接抛出异常。 检查类加载器代码如下:
// bootstrap加载器负责加载rt.jar,不是java编写,所以是null
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
获取Unsafe对象
虽然不能直接实例化获取Unsafe对象,但我们可以尝试通过反射从theUnsafe和构造器入手创建实例化对象:
- 方法一:反射获取字段即theUnsafe属性,就相当于获取到了Unsafe对象
- 方法二:反射获取构造器,再获取Unsafe实例
demo如下:
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class TemporaryFiles {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class unsafeClass = Unsafe.class;
// 第一种方式:通过构造器获取Unsafe实例
Constructor constructor = Unsafe.class.getDeclaredConstructor();
constructor.setAccessible(true);
Unsafe unsafe1 = (Unsafe) constructor.newInstance();
System.out.println(unsafe1);
// 第二种方法:通过字段获取Unsafe实例
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe2 = (Unsafe) theUnsafe.get(null);
System.out.println(unsafe2);
}
}
/*
输出:
sun.misc.Unsafe@4554617c
sun.misc.Unsafe@1540e19d
*/
利用Unsafe绕过JDK17+对反射的限制
反射的限制
根据 Oracle的文档,为了安全性,从JDK 17开始对java本身代码使用强封装,原文叫 Strong Encapsulation。任何对 java.* 代码中的非public变量和方法进行反射会抛出InaccessibleObjectException异常
JDK的文档解释了对java api进行封装的两个理由:
对java代码进行反射是不安全的,比如可以调用ClassLoader的defineClass方法,这样在运行时候可以给程序注入任意代码
java的这些非公开的api本身就是非标准的,让开发者依赖使用这个api会给JDK的维护带来负担
所以从JDK 9开始就准备限制对java api的反射进行限制(会出现警告),直到JDK 17才正式禁用(会报错)
利用Unsafe绕过限制
官方文档描述,java.* 的非公共字段和方法都无法反射获取调用了,但原文中还提到:
“Note that the sun.misc and sun.reflect packages are available for reflection by tools and libraries in all JDK releases, including JDK 17.”
即sun.misc和sun.reflect包下的我们是可以正常反射的,所以有个关键的类就可以拿来用来,就是 Unsafe