默认 ObjectInputStream 危险因不校验类名,攻击者可触发任意类的 readObject;JDK9+ 需显式设 ObjectInputFilter 白名单,或继承重写 resolveClass 动态校验。

为什么默认的 ObjectInputStream 是危险的
Java 原生序列化机制不校验类名,攻击者只要能控制输入流(比如反序列化网络请求、文件、缓存数据),就能触发任意类的 readObject 方法——包括恶意构造的 gadget 链。JDK 9+ 虽加了 ObjectInputFilter,但默认是 null,等同于放行一切。
用 ObjectInputFilter 设置白名单最直接
核心是给 ObjectInputStream 实例绑定一个过滤器,它会在反序列化每个类前检查是否允许加载。不是“黑名单拦截已知危险类”,而是“只允许明确列出的类”。
- 必须在构造
ObjectInputStream后、调用readObject()前设置,晚了无效 - 推荐用静态工厂方法
ObjectInputFilter.Config.createFilter()构建,支持通配符和长度限制 - 过滤规则示例:
com.example.model.*;!*;maxarray=100000表示只允许com.example.model下的类,其余全拒,数组长度上限 10 万 - 注意:通配符
*不匹配嵌套包,com.example.**才包含子包;!开头的规则优先级更高
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(
"com.myapp.dto.*;maxdepth=5;maxrefs=100"
));
继承 ObjectInputStream 自定义校验更可控
当需要动态判断(比如按租户隔离类路径)、或 JDK 版本低于 9 时,重写 resolveClass 是唯一可靠方式。
- 必须重写
resolveClass(ObjectStreamClass),不能只靠readObject前校验——因为类加载发生在该方法内 - 从
ObjectStreamClass中取类名用desc.getName(),别用forName()触发加载 - 白名单建议存在
Set<String>中,用contains()判断,避免正则性能抖动 - 遇到非法类名,抛
ClassNotFoundException(不是InvalidClassException),否则可能被绕过
class SafeObjectInputStream extends ObjectInputStream {
private static final Set<String> ALLOWED_CLASSES = Set.of(
"com.myapp.User", "com.myapp.Order"
);
SafeObjectInputStream(InputStream in) throws IOException { super(in); }
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
if (!ALLOWED_CLASSES.contains(desc.getName())) {
throw new ClassNotFoundException("Blocked: " + desc.getName());
}
return super.resolveClass(desc);
}
}
Spring、Jackson 这些框架也逃不开序列化风险
很多人以为用了 Spring Boot 就安全了——错。Spring 的 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)默认不启用反序列化防护;而 ObjectInputStream 直接暴露在 RedisTemplate 或 RmiClientSocketFactory 等底层组件中。
立即学习“Java免费学习笔记(深入)”;
- Spring Boot 2.6+ 默认禁用
Jackson的DefaultTyping,但若手动开启(比如配置enableDefaultTyping()),且没设白名单,照样中招 - Redis 存 Java 对象时若用
JdkSerializationRedisSerializer,就等于把ObjectInputStream暴露给 Redis 数据——务必换为GenericJackson2JsonRedisSerializer - 所有接收二进制流并调用
readObject()的地方,都要人工审计:RPC 接口、日志回放、测试 mock 数据、旧系统迁移脚本
白名单不是加个配置就完事。类名拼写错误、包路径变更、第三方库升级引入新 gadget 类,都会让防护失效。真正难的是持续维护那份允许列表——它得跟代码发布节奏同步更新,而不是写死在 config 文件里。


















