重写 resolveclass 是防范 java 原生反序列化注入最直接、最底层的手段,通过在类加载阶段拦截非法类名实现源头防御,适用于 java 7+,需严格校验包前缀、拒绝非常规类名并规范异常处理。

重写 resolveClass 是防范 Java 原生反序列化注入最直接、最底层的手段之一。它不依赖外部组件或 JVM 版本,适用于 Java 7 及以上所有环境,核心思路是在类加载阶段就拦截非法类名,从源头拒绝危险类型。
为什么 resolveClass 是关键拦截点
ObjectInputStream 在反序列化时,每遇到一个类描述符(ObjectStreamClass),都会调用 resolveClass() 获取对应 Class 对象。这个方法默认使用上下文类加载器加载类,但它是可被子类覆盖的——攻击者无法绕过你重写的逻辑,除非完全替换 ObjectInputStream 实例本身(而你正控制着这个实例的创建)。
安全重写的三个要点
必须同时满足以下条件,否则仍可能被绕过:
-
只放行明确信任的包路径前缀:例如
com.myapp.dto.、java.lang.,禁止通配符如com.*或模糊匹配 -
拒绝空名、数组类、内部类、匿名类等非常规类名:如
[Ljava.lang.String;、com.example.Foo$1、"" -
对异常处理严格:不能静默吞掉
ClassNotFoundException后 fallback 到父类逻辑;应统一抛出InvalidClassException并记录告警
典型安全实现示例
以下代码片段可直接嵌入业务中,无需额外依赖:
ObjectInputStream ois = new ObjectInputStream(inputStream) {
@Override
protected Class> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String name = desc.getName();
// 拒绝 null、空串、数组、内部类、危险前缀
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.model.") ||
name.startsWith("java.lang.") ||
name.startsWith("java.util.")) {
return super.resolveClass(desc);
}
throw new InvalidClassException("Unauthorized class: " + name);
}
};
配合其他措施才真正可靠
仅靠重写 resolveClass 不足以覆盖全部风险场景:
- 若反序列化发生在第三方库内部(如某些旧版缓存客户端、RPC 框架),你无法控制其 ObjectInputStream 创建过程,此时需升级或替换组件
- 某些 gadget 链利用
readObject、readResolve等钩子方法,它们在类加载后执行,需结合白名单+禁用高危反射(如-Dorg.apache.commons.collections.enableUnsafeSerialization=false) - 建议搭配网关层魔数
AC ED 00 05拦截,避免恶意流进入应用层
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











