许多后端系统在设计跨进程通信、本地缓存或状态持久化时,常假定传入的数据流是可信且受控的。正是这种“内部环境安全”的隐式假设,让未授权反序列化接口演变为高危的远程代码执行(RCE)通道。当反序列化器在缺乏类型范围约束的情况下重建对象时,攻击者可以通过构造包含恶意调用链(Gadget Chain)的载荷,在对象恢复属性或触发特定生命周期方法(如 readObject)的瞬间完成代码执行。

反序列化接口的代码风险成因
在 Java 原生序列化或各类第三方库(如 Jackson 多态反序列化、SnakeYAML、XStream)中,动态确定目标类型的机制为利用链构造提供了土壤。以 Java 原生接口为例,当代码直接暴露 ObjectInputStream.readObject() 时,类型转换发生在对象完全实例化之后:
// 危险示例:未做任何类型限制直接读取不可信输入
public Object processPayload(byte[] rawData) throws Exception {
try (ByteArrayInputStream bais = new ByteArrayInputStream(rawData);
ObjectInputStream ois = new ObjectInputStream(bais)) {
// 在完成显式强转前,目标类的构造逻辑与 readObject 已经执行完毕
return ois.readObject();
}
}
在上述逻辑中,哪怕代码后续仅将返回值强转为业务所需的 UserSession,恶意载荷中的 Gadget 也会在 ois.readObject() 执行期间触发。只要运行环境类路径(Classpath)中存在可利用的类库,攻击者就能借助链式反射调用甚至 JNDI 引用触发任意代码执行。
基于严格对象白名单的安全重构
根治此类问题的核心是前置类型决议,即在真正实例化对象之前对二进制流中的类标识符做强校验。
对于 Java 原生序列化,从 Java 9 起引入了标准的 ObjectInputFilter(JEP 290 机制,亦向后移植到了 Java 8 的高版本中)。重构时应直接绑定白名单过滤器,仅允许业务必需的基础类型与特定实体反序列化,并严格限制对象图的深度和数组大小。
import java.io.*;
public class SecureDeserializer {
// 仅允许基础类与指定的业务传输对象
private static final ObjectInputFilter STRICT_FILTER =
ObjectInputFilter.Config.createFilter(
"com.example.dto.UserSession;java.lang.String;java.lang.Number;!*"
);
public static Object safeRead(byte[] data) throws IOException, ClassNotFoundException {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
// 将过滤器挂载到当前流
ois.setObjectInputFilter(STRICT_FILTER);
return ois.readObject();
}
}
}
针对使用 Jackson 等 JSON 序列化库的场景,应避免全局开启 enableDefaultTyping(),改用显式注解(如 @JsonTypeInfo 配合 @JsonSubTypes)定义受信任的子类列表。
重构边界条件与副作用
- 类型演进影响:当实体类新增依赖其它非基础数据结构时,必须同步更新白名单模式串,否则会导致合法请求抛出
InvalidClassException。 - 回退与兼容:对于跨版本升级系统的临时过渡,应建立可配置的过滤器模式,但严禁将默认策略退化为通配允许。
CI/CD 静态规则检测与门禁落地
防御方案不仅依赖开发自觉,更需要将检测手段沉淀为流水线自动化门禁。使用基于抽象语法树(AST)的静态分析工具(如 Semgrep),能够在代码提交和 PR 审查阶段捕获危险的无保护反序列化调用。

Semgrep 静态审计规则配置
创建 .semgrep/rules/untrusted-deserialization.yml 规则文件,用于匹配缺乏 setObjectInputFilter 保护的 ObjectInputStream 实例:
rules:
- id: java-untrusted-deserialization-no-filter
languages: [java]
message: "检测到可能存在未受保护的原生反序列化调用。请确保配置 ObjectInputFilter 白名单。"
severity: ERROR
patterns:
- pattern: (ObjectInputStream $OIS).readObject(...)
- pattern-not-inside: |
$OIS.setObjectInputFilter(...);
...
$OIS.readObject(...);
流水线配置与阻断策略
在 CI 流水线(以 GitHub Actions 为例)中执行静态检测,并设置阻断级别:
name: Security SAST Gate
on:
pull_request:
branches: [main, release/*]
jobs:
sast-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Run Semgrep SAST
uses: returntocorp/semgrep-action@v1
with:
config: .semgrep/rules/
# 发现 ERROR 级别规则立即阻断流水线构建
auditOn: "pull_request"
continue-on-error: false
误报处理与回滚策略
- 误报处置:如果部分单元测试需要模拟畸形流或已在严格隔离的离线环境运行,必须采用精确的代码行级忽略(如在代码中添加
// nosemgrep: java-untrusted-deserialization-no-filter),并强制要求提交对应的安全审批凭证,杜绝全局禁用规则。 - 门禁回滚:当新引入的白名单规则阻断了关键产线迭代且确认属于规则误伤时,应通过 CI 环境变量降级该规则为
WARNING级别,恢复分支合入,同时建立工单在限定周期内修正规则匹配模式,避免安全基线失守。
- it2021
- it2021.com
- test
- 代码执行
- 恶意载荷
- 规则检测
- 远程代码执行

1F
白名单过滤这个思路确实比事后排查靠谱多了