反序列化漏洞有个很拧巴的特点:业务侧只觉得自己在"存对象、传对象、缓存对象",安全侧看到的却是------不可信数据一旦变成对象,就可能顺着构造函数、魔术方法、魔术接口,一路跑到命令执行 。于是报告标题经常直接写 RCE,开发一脸懵:"我就是 readObject / unserialize / pickle.loads 了一下啊?"
是的,问题就出在这一下。
一、先讲所有语言共用的那层道理
1. 序列化是什么
把内存里的对象变成可传输、可存储的字节或文本;反过来叫反序列化。
用途正当:缓存、RPC、Session、消息队列、SSO Token、游戏存档、任务任务体......
2. 危险协议 vs 数据协议
若格式的设计目标是"恢复出完整对象并执行类型相关逻辑",它就更危险。
若格式只是数据结构(JSON/PB 等)再手动映射到对象,通常安全得多------前提是你不要在映射后又搞一套危险动态调用。
3. 利用链(Gadget Chain)是什么
攻击者往往不能直接"命令字符串",但能控制反序列化出来的对象图。
若 classpath/环境里存在一些"看似无害、组合起来危险"的类,它们的反序列化回调(Java readObject 等)、魔术方法(PHP __wakeup/__destruct)、还原逻辑(Python __reduce__)可能拼成一条调用链,最终摸到 Runtime.exec、system、文件写入等。
所以反序列化漏洞常常是:
入口(反序列化用户数据) + 材料(可用 gadget 类) + 结果(RCE/读文件/JNDI...)
修入口最重要;减材料(升级、移出危险依赖)是纵深;监控结果是兜底。
4. 共同防御原则
- 不要反序列化不可信数据;
- 必须传数据时,用 JSON 等纯数据格式 + 校验;
- 若不得不保留原生反序列化:签名/加密、强类型白名单、网络隔离;
- 升级组件,打破已知利用链;
- 运行时最小权限。
下面三条线,都是这五条的方言版。
二、Java 线:历史最久、事故最响、链最工业
1. 常见入口
ObjectInputStream.readObject()及同类;- 会话持久化、RMI、JMX、自定义 RPC;
- 一些中间件/协议自带的 Java 序列化;
- 假"加密"的 Cookie/参数里塞序列化二进制(Base64 一眼能认)。
特征:数据常以 ac ed 00 05(Java 序列化魔数)开头,或 Base64 后以 rO0 一类开头------审计与检测常靠这个。
2. 为什么特别容易打到 RCE
Java 生态依赖树深,历史上 Commons Collections、其他流行库等与调用链组合的问题被广泛研究;安全圈有成熟的研究工具生态(授权测试中常被提及)。
对防守方这意味着:你以为只反序列化了自己的业务类,classpath 里却躺着别人的弹药。
3. 利用条件(理解用)
- 存在反序列化入口且数据用户可控;
- 目标 classpath 有可用链;
- JDK/组件版本与安全过滤器(JEP290 等)是否挡住。
不是"有序列化就一定挂",但"有不可信 readObject"应默认高优先级审计。
4. 防御讲透
(1)能不用就不用
内部缓存优先 JSON/Hessian 安全模式/PB 等,并明确禁止 Java 原生序列化进公网接口。
(2)JEP 290 反序列化过滤(JDK 9+,低版本有backport思路)
配置允许的类白名单,拒绝其余。这是官方级缓解,值得做进统一工厂。
(3)看起来像白名单的库
历史上有过"黑名单过滤"被绕过的故事;白名单优于黑名单。自己维护过滤器要跟版本。
(4)升级与下线危险依赖
就算暂时关不掉入口,也要评估已知相关组件版本,减少可用链。依赖扫描进 SLA。
(5)网络与身份
RMI/JMX 绝不要对公网;管理口鉴权;进程权限最小。
(6)检测
流量中识别 Java 序列化魔数打入业务参数;RASP 监控反序列化敏感调用;主机侧看 Java 进程起谁。
5. 授权测试怎么做
- 找 Base64/
ac ed特征入口; - 在授权环境评估是否可构造危害(点到为止);
- 报告写清入口、JDK/组件信息、修复为"移除反序列化 + 过滤 + 升级";
- 不在报告附可用攻击链细节给无关方扩散。
6. Java 线一句话
不可信
readObject= 按 RCE 候选处理;用白名单过滤与淘汰原生序列化双管齐下。
三、PHP 线:unserialize 与魔术方法的剧场
1. 常见入口
unserialize($_COOKIE/$_POST/...);- 缓存、队列里存了 PHP 序列化串;
- Session 特定 handler;
- 历史问题:Phar 元数据反序列化(文件操作触发)------与"路径可控"组合很恶心。
PHP 序列化字符串常带 O:4:"Class":... 这类形态,较好认。
2. 利用机制直觉
PHP 对象在反序列化/销毁时会调魔术方法:__wakeup、__destruct、__toString 等。
若某个类的魔术方法里存在危险操作(删文件、写文件、调驱动),攻击者通过精心构造对象属性,把控制流拐过去。
流行 CMS、框架、插件里若存在"材料类",就会形成公开链;升级插件与框架因此至关重要。
3. Phar 反序列化(要点)
对某些文件函数传入 phar:// 路径时,可能解析 Phar 文件并反序列化元数据。
于是:文件上传(内容可控)+ 文件操作(路径可控) 也能间接触发反序列化。
修法:升 PHP;限制 phar;用户输入禁止进危险文件函数;上传内容当不可信二进制。
4. 防御讲透
(1)禁止反序列化用户输入
需要结构化数据 → json_decode + 校验字段。
(2)allowed_classes 选项
unserialize 的 options 限制允许类(版本需支持)。白名单尽量空或仅少数 DTO,且 DTO 无危险魔术方法。
(3)升级 CMS/框架/插件
PHP 反序列化事故大量出在应用层材料,不单是语言本身。
(4)Session 与缓存
别把不可信数据塞进会自动反序列化的存储。
(5)检测
参数中出现 O:/a: 序列化特征;异常文件操作与 phar://。
5. 授权测试怎么做
- 搜
unserialize;抓包找序列化形态; - 评估是否用户可控;
- 结合版本与组件查已知问题(授权范围内);
- 修复验证:入口改为 JSON,或
allowed_classes生效。
6. PHP 线一句话
unserialize用户数据默认按代码执行风险看;JSON 化 + 类白名单 + 插件升级。
四、Python 线:pickle 不配得到信任
1. 常见入口
pickle.loads/cPickle;- 有时包在 Base64、Redis 缓存、任务队列(早期 celery 配置不当等);
yaml.load使用不安全 Loader(偏 YAML 反序列化,常与 pickle 同等对待讲 RCE);shelve、某些 RPC。
2. 为什么说 pickle "设计上就能跑代码"
pickle 协议允许对象在还原时执行重建逻辑(例如通过 __reduce__ 返回可调用对象与参数)。
因此:对不可信数据 pickle.loads ≈ 远程代码执行,不是夸大,是模型问题。
官方文档长期以来也提示:pickle 不安全,不要用于不可信数据。这条被忽略的次数令人心碎。
3. YAML 的坑
yaml.load 无 SafeLoader 时,历史版本可构造危险对象。
应使用 safe_load 或明确 SafeLoader。
JSON 能解决的别用 YAML 动态类型。
4. 防御讲透
(1)不可信数据禁用 pickle
换 JSON;需要类型时手动建模(pydantic 等)校验。
(2)缓存与队列
Redis 里别塞 pickle;用 JSON 消息。历史系统迁移要切格式并拒老数据或签名。
(3)签名若必须用 pickle
HMAC 校验完整性 + 仅内网 + 仍要知道:密钥泄漏则仍 RCE。签名是缓解不是根治。
(4)yaml.safe_load
全局搜 yaml.load,改安全加载。
(5)检测
代码扫描 pickle.loads、yaml.load(;运行时监控。
5. 授权测试怎么做
- 代码审计优先,比盲打高效;
- 发现入口即高危;
- 修复对照:JSON + 校验,或删除接口。
6. Python 线一句话
pickle 只吃你自己产的、同信任域的数据;否则不要 loads。
五、三条线对照总表
| Java | PHP | Python | |
|---|---|---|---|
| 典型 API | readObject |
unserialize |
pickle.loads |
| 危险来源 | 入口 + classpath 链 | 魔术方法 + 应用类 | 协议本身可执行 |
| 额外坑 | RMI/JMX、过滤绕过 | Phar 间接触发 | yaml.load |
| 主修复 | 停用原生序列化 + 白名单过滤 | JSON + allowed_classes | 禁用 pickle + JSON |
| 特征 | ac ed / rO0 |
O:数字: |
pickle 二进制/协议头 |
六、"序列化"家族的近亲(避免漏报)
- .NET BinaryFormatter 等(同样危险默认值故事);
- Android 某些 Parcel/Intent 问题(另一专题);
- Jackson/Fastjson 等 多态类型处理不当导致的"反序列化貌" RCE(autotype)------不是 Java 原生序列化,但报告常写反序列化;修法是禁 autoType、升级、白名单。
若写企业规范,建议标题用 "不安全反序列化与危险类型恢复",把 JSON autotype、pickle、Java 原生、PHP unserialize 放进同一制度。
七、从漏洞到 RCE 的共用叙事
text
用户可控字节进入反序列化入口
→ 恢复对象图 / 触发还原逻辑
→ 利用链触达危险函数
→ 命令执行 / 写文件 / JNDI / 读密钥
→ 权限与横向
打断:
- 入口消亡或改安全格式;
- 过滤与升级打断链;
- 权限与网络减损。
八、组织级落地
- 制度:禁止公网接口原生反序列化;例外需架构签字。
- 扫描:Semgrep/IDE 插件规则扫危险 API。
- 依赖:组件漏洞进补丁 SLA(本系列补丁文)。
- 运行时:RASP/EDR 关注反序列化后起进程。
- 培训:用"三条线各一个靶场 Demo"做内训,比只念 CVE 号有效。
九、收尾
Java 线教你:classpath 里的类都是弹药,入口要关,过滤要白名单。
PHP 线教你:魔术方法与 Phar 会把"读文件/上传"变成反序列化。
Python 线教你:pickle 从根上就不适合处理不可信数据。
三条线方言不同,母语只有一句:
反序列化的是权力,不是数据;
不可信的输入,不配得到这份权力。
今晚若只做一件事:在三个主要后端仓库分别搜 readObject/ObjectInputStream、unserialize、pickle.loads/yaml.load,把命中列成表,标出数据来源是否用户可控。
这张表,就是你们离"反序列化 RCE"真实距离的地图。
附:安全替代
| 需求 | 更安全常见选择 |
|---|---|
| API 传结构化数据 | JSON + schema 校验 |
| 缓存 | JSON/MessagePack 等数据格式 |
| RPC | 明确 IDL 的 gRPC/PB 等 |
| 配置 | JSON/TOML + 校验,慎用不安全 YAML |