resolveclass 是最底层、最可靠的反序列化防护手段,因其在类加载前拦截非法类名,不依赖 jvm 版本且不受 gadget 链绕过影响;需严格满足白名单精确匹配、拒绝非常规类名、异常处理严格三项要求。

直接重写 resolveClass 是最底层、最可靠的防范手段——它在类加载前就拦截非法类名,不依赖 JVM 版本,也不怕 gadget 链绕过。
为什么 resolveClass 是关键拦截点
ObjectInputStream 每读到一个类描述符,就会调用 resolveClass() 去加载对应 Class。这个方法默认用上下文类加载器,但允许子类覆盖。只要你控制了 ObjectInputStream 实例的创建,攻击者就无法跳过你写的校验逻辑。
- 它发生在反序列化流程最早期,早于字段还原、
readObject()执行、甚至早于 gadget 链触发 - 不走 JVM 全局过滤器(如
jdk.serialFilter),不受其绕过缺陷影响 - 适用于 Java 7 及以上所有版本,老系统也能用
安全重写的三个硬性要求
只满足其中一两项,仍可能被绕过。必须全部做到:
-
白名单精确匹配:只放行明确信任的包路径,例如
com.myapp.dto.、java.lang.;禁用通配符模糊匹配(如com.*) -
拒绝非常规类名:空字符串、数组类型(
[Ljava.lang.String;)、内部类(com.example.Foo$1)、匿名类、以危险前缀开头的类(如org.apache.commons.collections、groovy.) -
异常处理严格:非法类必须抛
ClassNotFoundException或InvalidClassException;不能静默 fallback 到父类逻辑,否则等于没拦
典型安全实现写法
以下代码可直接嵌入业务,无需额外依赖:
(注意:不要用 Class.forName(),避免提前触发类加载;用 super.resolveClass(desc) 委托给父类完成真实加载)
ObjectInputStream ois = new ObjectInputStream(inputStream) {
@Override
protected Class> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
String name = desc.getName();
// 拒绝非法类名
if (name == null || name.isEmpty() ||
name.startsWith("[") || name.contains("$") ||
name.startsWith("org.apache.commons.collections") ||
name.startsWith("groovy.") ||
name.startsWith("com.sun.rowset")) {
throw new InvalidClassException("Blocked class: " + name);
}
// 白名单校验(精确前缀)
if (name.startsWith("com.myapp.dto.") ||
name.startsWith("java.lang.") ||
name.startsWith("java.util.")) {
return super.resolveClass(desc);
}
throw new InvalidClassException("Unauthorized class: " + name);
}
};
配合其他措施才真正牢固
resolveClass 是核心防线,但不是唯一防线:
- Web 层禁止用
request.getInputStream()直接喂给ObjectInputStream - Session 存储别用 Java 序列化,改用 JSON 或加密字符串
- 清理高危依赖:移除
groovy、commons-beanutils、旧版c3p0等 - JVM 启动加参数:
-Dorg.apache.commons.collections.enableUnsafeSerialization=false
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











