反序列化入门
反序列化入门
本文适合
第一次接触反序列化的学习者。学完你能:理解序列化和反序列化的概念,识别反序列化入口,理解为什么反序列化危险
反序列化漏洞发生在应用把不可信数据还原成对象,并在还原过程中触发了危险逻辑。它的重点不是"数据格式复杂",而是对象生命周期中可能自动执行代码。
序列化是什么
序列化是把对象变成可保存或传输的数据。
反序列化是把这些数据还原成对象。
常见场景包括:
- Cookie 保存对象。
- Session 保存对象。
- 缓存系统保存对象。
- 消息队列传输对象。
- API 接收二进制或文本对象格式。
常见语言和格式包括 PHP serialize、Java serialization、Python pickle、Ruby Marshal、.NET BinaryFormatter 等。
危险在哪里
危险不在"还原数据"本身,而在还原对象时触发的魔术方法、构造/析构逻辑、属性访问、比较、字符串转换等行为。
例如 PHP 里常见魔术方法包括:
__wakeup
__destruct
__toString
__callJava 里常见入口是 readObject。
Python pickle 本身就能表达对象构造和调用关系,因此尤其危险。
gadget chain 是什么
gadget 是程序或依赖库里已有的一小段可利用逻辑。
gadget chain 是把多个对象和方法调用串起来,让反序列化过程自动走到敏感操作。
敏感操作可能是:
- 文件读取。
- 文件写入。
- 命令执行。
- 模板渲染。
- SQL 查询。
- 网络请求。
反序列化题常常不是自己写完整执行逻辑,而是利用现有类之间的调用关系。
观察入口
看到这些迹象要想到反序列化:
- Cookie 或参数里有对象结构。
- Base64 解码后出现类名、字段名。
- PHP 字符串里有
O:、s:、a:。 - Java 数据以
ac ed 00 05开头。 - Python pickle 出现
pickle、__reduce__、奇怪二进制对象。 - 报错中出现 unserialize、deserialize、readObject。
反序列化和签名
很多系统会给序列化数据加签名。
签名正确时,服务端才反序列化。
如果签名密钥泄露、算法弱、校验缺失,攻击者才能构造对象。
所以反序列化题常常还会连接 编码、哈希与加密、JWT基础 或弱密钥问题。
常见误区
- 看到 Base64 就直接解码,不判断内部对象格式。
- 以为反序列化一定等于命令执行。
- 不找可用类和魔术方法,只套通用 payload。
- 忽略签名、压缩、加密这一层包装。
- 把 JSON 参数误认为一定安全,忽略应用自定义对象绑定。
做题时的归类问题
- Cookie 或参数里有
O:、s:、a:等 PHP 序列化特征,先回到 反序列化入门。 - Base64 解码后出现类名、字段名,先回到 反序列化入门。
- Java 数据以
ac ed 00 05或rO0AB开头,先回到 反序列化入门。 - 报错中出现 unserialize、deserialize、readObject,先回到 反序列化入门。
- 需要构造 gadget chain 利用已有类,先回到 反序列化进阶。
一句话判断
Cookie、参数、Session、消息体或上传文件中出现对象结构,服务端会把它还原成对象,并可能触发魔术方法、构造/析构逻辑或危险库函数时,就按反序列化漏洞分析。
反序列化入门题先证明"数据被还原成对象",再证明"对象生命周期能走到危险操作"。
题目中常见信号
- PHP 序列化:
O:4:"User":...、a:1:{...}、s:5:"admin"。 - Java 序列化:十六进制
ac ed 00 05,Base64 常见前缀rO0AB。 - Python pickle:代码里出现
pickle.loads、pickle.load、__reduce__。 - Cookie 或参数 Base64 解码后出现类名、属性名。
- 报错出现
unserialize()、deserialize()、readObject()。 - 题目给出源码,里面有
__wakeup、__destruct、__toString、readObject等入口。
核心概念
反序列化漏洞有三层:
入口:不可信数据进入 unserialize / readObject / pickle.loads
链路:对象还原时自动调用方法或访问属性
终点:文件读写、命令执行、模板渲染、SQL 查询、SSRF 等敏感操作序列化格式只是载体。真正要找的是 source 到 sink 的调用链。
最小分析流程
- 找入口:哪里调用了反序列化函数。
- 找包装:数据是否经过 Base64、URL 编码、压缩、签名或加密。
- 找类:源码或依赖里有哪些可被构造的类。
- 找魔术方法或自动触发点。
- 找危险操作:文件、命令、模板、网络、数据库。
- 构造最小对象,只触发无害验证,例如写入
/tmp/poc或输出固定字符串。 - 再根据题目目标改成读 flag、写 shell 或执行命令。
最小验证示例
PHP 对象格式识别:
import base64
raw = "Tzo0OiJVc2VyIjoxOntzOjQ6Im5hbWUiO3M6NToiZ3Vlc3QiO30="
data = base64.b64decode(raw).decode()
print(data)输出:
O:4:"User":1:{s:4:"name";s:5:"guest";}说明这不是普通字符串,而是 PHP 序列化对象。
Java 前缀识别:
import base64
data = base64.b64decode(cookie)
print(data[:4].hex())如果输出:
aced0005就进入 Java 反序列化方向。
常见利用 / 解题路线
路线总览:
路线一:PHP 魔术方法
unserialize 入口 -> __wakeup/__destruct/__toString -> 文件读写或命令执行先构造与源码类名、属性名完全一致的对象,再测试属性是否可控。
路线二:Java ysoserial 链
识别 ac ed 00 05 / rO0AB -> 判断依赖库 -> ysoserial 生成 payload -> Base64/Cookie 发送Java 题常见于 Shiro rememberMe、老框架组件和 Commons Collections 依赖。
路线三:Python pickle
pickle.loads 入口 -> __reduce__ 指定可调用对象 -> 反序列化时执行pickle 本身就能表达调用关系,风险比 JSON 高很多。
路线四:签名或包装绕过
如果序列化数据带签名,先找密钥泄露、弱密钥、算法混淆或校验缺失。没有通过签名,服务端通常不会进入反序列化。
常见失败原因
- 只 Base64 解码一次就停:可能还有 URL 编码、压缩、JSON 包装或签名。
- 类名和属性名不匹配:序列化 payload 必须符合目标语言格式和源码定义。
- 忽略魔术方法触发时机:有的链在反序列化时触发,有的在对象销毁或字符串转换时触发。
- 直接套通用 payload:依赖库不存在时 ysoserial/phpggc 链不会生效。
- 忽略签名校验:签名失败时 payload 根本不会被反序列化。
- 把 JSON 当绝对安全:一些框架会把 JSON 绑定成对象并触发类型解析。
迷你案例
题目 Cookie:
profile=Tzo0OiJVc2VyIjoxOntzOjQ6Im5hbWUiO3M6NToiZ3Vlc3QiO30=第一步解码:
import base64
print(base64.b64decode("Tzo0OiJVc2VyIjoxOntzOjQ6Im5hbWUiO3M6NToiZ3Vlc3QiO30=").decode())得到:
O:4:"User":1:{s:4:"name";s:5:"guest";}第二步读源码,发现:
class User {
public $file;
function __destruct() {
echo file_get_contents($this->file);
}
}第三步构造:
O:4:"User":1:{s:4:"file";s:9:"/flag.txt";}Base64 后替换 Cookie。若页面输出 flag,说明链路是:
Cookie -> base64 decode -> unserialize -> User::__destruct -> file_get_contents