Java反序列化默认全放行,需用ObjectInputFilter白名单校验:构造ObjectInputStream后立即设置,支持字符串规则(如com.example.model.**)和动态实现,低版本须重写resolveClass。

Java 反序列化默认允许加载任意类,攻击者只要控制输入流(比如 HTTP 请求体、Redis 缓存、MQ 消息),就能触发 gadget 链执行命令。ObjectInputFilter 是 JDK 9 起引入的白名单机制,它不是“开关”,而是必须显式设置、且只能在 readObject() 前生效的校验入口。
必须在 readObject() 前设置过滤器
ObjectInputFilter 默认为 null,等同于全放行。构造完 ObjectInputStream 后,必须立即调用 setObjectInputFilter(),晚一步就失效:
- 过滤器一旦设置不可更改,也不能靠 try-catch 捕获后再设
- 不要在工具方法里延迟设置——比如封装成工具类但把 filter 设置放在 read 之后
- 推荐在构造后紧跟着设置,例如:
ois.setObjectInputFilter(Config.createFilter("com.example.dto.*;maxdepth=5"));
用 Config.createFilter 写清晰白名单规则
字符串模式是主流方式,语法简洁、可配置、易审计。关键点在于类名匹配逻辑和限制参数协同:
-
com.example.model.*只匹配一级子包,如com.example.model.User,不包括com.example.model.sub.Order;要用com.example.model.**才覆盖嵌套包 - 规则顺序有优先级:
!*放最后,com.example.**放前面;以!开头的拒绝规则优先级更高 -
maxdepth=5控制对象图嵌套深度,maxarray=10000防数组爆炸,maxrefs=200限制引用数量——这些不能替代白名单,但能防 DoS
自定义 ObjectInputFilter 实现动态判断
当白名单需运行时决定(如按租户、环境、权限动态加载),或要记录日志、做反射校验、检查是否 record 类型时,就得实现接口:
立即学习“Java免费学习笔记(深入)”;
-
checkInput(FilterInfo info)返回ALLOWED/REJECTED/UNDECIDED - 通过
info.serialClass()获取待反序列化的 Class,再取.getName()判断,避免调用forName()提前触发类加载 - 对
clazz == null或非法类名,直接返回REJECTED,不要抛异常(filter 不处理异常) - 建议缓存已验证类名,避免重复反射开销;白名单集合用
Set<String>而非正则,保证 O(1) 查找
低版本 JDK 或特殊场景用 resolveClass 重写
JDK 8 及以下没有 ObjectInputFilter,或者你用了自定义序列化协议、需要更底层控制时,必须继承 ObjectInputStream 并重写 resolveClass():
- 这是类加载发生的唯一位置,所有校验必须在这里做,
readObject()前的校验无效 - 从
ObjectStreamClass desc中取类名用desc.getName(),不是Class.forName() - 非法类必须抛
ClassNotFoundException,抛InvalidClassException可能被绕过 - 白名单建议硬编码在
static final Set中,或从配置中心拉取后初始化一次


















