PHP 反序列化我前后啃了很久,这篇把散的知识点收成一张图。具体某条链子的完整复现我单独写过(ThinkPHP 5.1.37 那篇),这篇专注方法论:链子怎么找、怎么串、怎么绕。
反序列化为什么危险
unserialize($data) 把字符串还原成对象时,会自动触发一系列魔术方法。如果 $data 可控,攻击者就能自定义对象的类和属性——类的魔术方法会被执行,属性值全由攻击者控制。危险的不是 unserialize 本身,是它点燃的那串自动调用。
魔术方法速查
| 方法 | 触发时机 | POP 链角色 |
|---|---|---|
__destruct | 对象销毁 | 最常见引爆点 |
__wakeup | unserialize 时 | 引爆点(可被绕过) |
__construct | new 时 | 反序列化不触发,别混淆 |
__toString | 对象被当字符串用 | 常用跳板 |
__call | 调不存在的方法 | 跳板 |
__get / __set | 访问不存在的属性 | 跳板 |
__invoke | 对象被当函数调 | 常见末端执行点 |
串链子的思路
我从尾往前找,比较不容易迷路:
- 找终点:先锁定危险的”动作函数”——
file_put_contents、call_user_func、eval、system这类。 - 找倒数第二环:哪个类的魔术方法会触达终点,且参数来自属性(属性可控)。
- 向前接力:反复上一步,每一环只需要回答”这个类的哪个魔术方法能把可控属性传给下一环”。
- 接通引爆点:直到某一环能被
__destruct/__wakeup直接触达。
关键心法:属性全可控是反序列化的精髓——每一环的 $this->xxx 都是你说了算。
phar 反序列化
phar:// 伪协议在文件操作函数(file_exists、file_get_contents 等)里被调用时,会反序列化 phar 包的 metadata。价值在于:不需要 unserialize 入口,只要有个文件操作函数 + 能上传 phar 文件(可伪装成图片)就能打。
file_exists('phar://evil.jpg/test') → 触发 metadata 反序列化
两个实用绕过
__wakeup绕过(CVE-2016-7124):属性个数大于真实个数时__wakeup不执行。O:4:“Demo”:2:{…} 把 2 改成 3。- 字符逃逸:字符串替换导致长度变化时,精心设计前后值可以让属性”逃出”原有边界,吞掉后续序列化内容。分两种情况:替换变长→后面补位逃逸;替换变短→前面吃字符逃逸。
审计时的检查清单
- 全局搜
unserialize(,看参数是否可控(COOKIE/POST/文件内容)。 - 搜
phar://可用的文件操作函数。 - 框架项目直接分析已知 POP 链类(ThinkPHP 的 Windows/Request/Conversion 这些)。
- 找不到现成链就按”从尾往前”法自己串。
小结
反序列化漏洞的上限取决于项目里有什么类可串,所以它和代码审计是绑死的。Xdebug 单步跟踪(我上篇写的环境)在这里极其好用:跟一遍链子胜过读十遍文章。