反序列化防护的整体思路
反序列化漏洞的本质是「不可信数据被当作对象结构还原」:攻击者构造恶意序列化串,在还原过程中触发类的魔术方法或 gadget 链,最终执行代码。防护不是维护一份黑名单,而是四层收口:优先换掉原生序列化协议、对可还原类型做白名单、运行期加类过滤、入口对序列化数据做签名校验。改造后必须回归对象兼容性与异常路径。
基本信息
| 高风险语言点 | Java 原生序列化与 XStream、PHP unserialize、Python pickle、.NET BinaryFormatter |
|---|---|
| 第一层 | 协议替换:新接口改用 JSON 等纯数据格式 |
| 第二层 | 类型白名单:只允许业务 DTO 包可被还原 |
| 第三层 | 运行期过滤:Java ObjectInputFilter、PHP allowed_classes |
| 第四层 | 边界校验:对序列化串做 HMAC 签名并校验时效 |
| 回归重点 | 对象兼容性、异常路径、历史数据的反序列化 |
分语言配置示例
分语言配置示例
PHP:unserialize($data, ['allowed_classes' => ['SafeClass']]) # 只放行白名单类
Java:ObjectInputFilter 限制可反序列化类(JDK 9+ 可用)
Python:接口层禁用 pickle,只走 json;确需时对数据做 HMAC 签名校验
.NET:避免 BinaryFormatter,改用 System.Text.Json 等数据格式
通用:入口对序列化数据做签名校验,防止被篡改后重放Java ObjectInputFilter 示例
Java ObjectInputFilter 示例
// 只允许业务 DTO 包与 java.base 模块,其余一律拒绝
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*");
ObjectInputStream ois = new ObjectInputStream(input);
ois.setObjectInputFilter(filter);
Object obj = ois.readObject(); // 过滤在读取阶段生效,命中拒绝会抛 InvalidClassException四层防护与落地动作
| 层次 | 目标 | 落地动作 |
|---|---|---|
| 协议替换 | 缩小原生序列化的使用面 | 新接口用 JSON 或 Protobuf,关闭类型元数据 |
| 类型白名单 | 限制可被还原的类 | 只允许业务 DTO 包,拒绝通配与反射类 |
| 运行期过滤 | 阻断 gadget 链 | 设置 ObjectInputFilter 或 allowed_classes 清单 |
| 边界校验 | 防止数据被篡改重放 | 对序列化串做 HMAC 签名并校验时间戳 |
| 可观测 | 发现攻击尝试 | 记录拒绝事件与来源 IP,接入告警 |
典型使用场景
- 新接口设计评审:确定序列化协议与数据边界
- 老系统改造排期:先加运行期过滤止血,再排期换协议
- RMI、HTTP 隧道、缓存与消息队列场景的反序列化点排查
- 引入第三方组件时复核其默认转换器与反序列化入口
- 应急加固:短时间内用过滤与规则降低利用面
- 审计整改举证:说明哪一层防护已落地、哪一层仍在排期
旧系统短期无法改造时,可用 WAF 规则与类加载层黑名单缓解,但要登记为技术债并明确整改时间,否则缓解措施会变成长期现状。
gadget 黑名单只能挡已知链条,新的利用链一出现就失效。白名单是能收口的思路,黑名单只适合作为过渡手段。