跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

怎么利用 ObjectInputStream 配合反序列化过滤器防御恶意对象注入攻击

ObjectInputStream 默认不校验类,反序列化即执行任意代码;JDK 9+ 必须显式设置 ObjectInputFilter,null 值等于全放行;低于 JDK 9 或需动态白名单时应重写 resolveClass();全局 jdk.serialFilter 易被绕过,不可单独依赖。 ObjectInputStream 默认不做类校验,直接反序列化等于执行任意代码 只要攻击者能控制输入流(比如 HTTP 请求体、Redis 缓存值、MQ 消息),
ObjectInputStream
就会按字节流里的类名动态加载类,并调用其
readObject()
方法——而很多类(如
AnnotationInvocationHandler
、
BadAttributeValueExpException
)的该方法内部会触发反射、命令执行或表达式解析。这不是 bug,是 Java 序列化机制的设计本质: 反序列化即执行 。 JDK 9+ 必须显式设置 ObjectInputFilter,null 值 = 全放行
ObjectInputFilter
是 JDK 9 引入的 JEP 290 机制,但它默认为
null
,等同于没开。必须在构造
ObjectInputStream
实例后、调用
readObject()
前,用
setObjectInputFilter()
绑定过滤器,晚了就无效。 推荐用
ObjectInputFilter.Config.createFilter()
构建规则字符串,例如:
"com.myapp.dto.*;maxdepth=5;maxrefs=100;maxarray=50000"
通配符
*
不匹配子包,要写
com.myapp.**
才覆盖嵌套路径 规则顺序重要:
!*
放最后,
com.myapp.**
放前面;以
!
开头的规则优先级更高 数组长度、对象图深度、引用次数等限制能防 DoS,但不能替代类白名单 低于 JDK 9 或需动态判断时,必须重写 resolveClass() JDK 8 及更早版本没有
ObjectInputFilter
,且某些场景(如多租户系统需按租户切换白名单)必须动态校验。这时唯一可靠方式是继承
ObjectInputStream
并重写
resolveClass()
。 CentOS Stream 9 CentOS Stream 9是基于RHEL 9技术路线的持续交付版本,适合需要贴近RHEL 9生态的软件开发、系统集成和测试环境。它相比传统CentOS Linux更靠近上游开发过程,用户可以更早看到RHEL 9后续小版本中的软件包变化。CentOS Stream 9仍是当前可用的官方版本线之一,适合对稳定性和新功能之间有平衡需求的团队使用。 下载 不能只靠
readObject()
前检查——类加载发生在
resolveClass()
内部 从
ObjectStreamClass desc
取类名用
desc.getName()
,别用
Class.forName()
触发提前加载 白名单建议用
Set
存储,
contains()
查找,避免正则匹配带来的性能抖动 非法类名必须抛
ClassNotFoundException
,抛
InvalidClassException
可能被绕过 全局 jdk.serialFilter 参数容易被绕过,不建议单独依赖 通过 JVM 参数
-Djdk.serialFilter
或
System.setProperty("jdk.serialFilter", "...")
设置全局过滤器,看似省事,但有硬伤: 对已重写
resolveClass()
的子类无效——代码里手动调用
super.resolveClass()
就绕过了 filter 无法区分不同业务上下文(比如管理后台和用户接口需要不同白名单) 老项目常混用
sun.misc.Unsafe
、
AtomicReferenceFieldUpdater
等非标准反序列化路径,这些不走
filterCheck()
某些 gadget 链在字段反序列化完成后才触发(如
InvokerTransformer
),此时 filter 早已退出 真正关键的点是:过滤必须发生在类加载那一刻,且不能被子类逻辑绕过;白名单粒度要到具体类名或精确包路径,模糊规则(如
java.util.*
)等于敞开大门。

相关文章