本文目录导读:

- 第一层:什么是序列化与反序列化?
- 第二层:危险的“魔术方法”
- 第三层:POP链(Property-Oriented Programming,面向属性编程)
- 完整的攻击流程(漏洞如何产生)
- 一个最小化的示例(便于理解)
- 总结与防御(为什么会有这个漏洞?)
PHP反序列化漏洞(通常称为PHP对象注入漏洞)的核心原理可以概括为一句话:用户能够控制传入反序列化函数的参数,导致程序在反序列化过程中自动触发某些危险方法(魔术方法),从而产生代码执行、文件操作或SQL注入等危害。
为了让你彻底理解,我把它拆解成三个层面的原理:
第一层:什么是序列化与反序列化?
- 序列化(Serialization):把内存中的对象(Object)转换成可以存储或传输的字符串(String)格式。
O:4:"User":1:{s:4:"name";s:5:"admin";}。 - 反序列化(Unserialize):把这个字符串恢复成内存中的对象。
关键点:反序列化本身只是数据格式的转换,并不危险,危险的是反序列化之后,对象被自动触发的操作。
第二层:危险的“魔术方法”
PHP 中有一类方法叫做“魔术方法”(Magic Methods),它们的命名有特殊含义,会在特定时机被自动调用,不需要你手动写代码去调用。
当反序列化发生时,以下魔术方法会被触发(这是漏洞利用的“引爆点”):
| 魔术方法 | 触发时机 | 常见危害 |
|---|---|---|
__wakeup() |
反序列化时(unserialize()执行时)被立即调用 |
通常用于初始化,但攻击者可以构造属性,让它去执行恶意代码。 |
__destruct() |
对象被销毁时(脚本结束或unset())被调用 |
最常见的利用点,因为脚本执行完必然会触发它。 |
__toString() |
当对象被当做字符串使用时 | 常用于拼接SQL语句或读取文件,触发危险文件操作。 |
__call() |
调用不存在的或不可访问的方法 | 可能被用来转发请求或拼接恶意命令。 |
__get() |
读取不可访问的属性时 | 用于敏感信息泄露。 |
核心逻辑:攻击者需要找到代码中是否存在定义了危险魔术方法的类,并且控制这些方法中操作的数据(如属性值)。
第三层:POP链(Property-Oriented Programming,面向属性编程)
很多时候,仅仅触发一个魔术方法还不够危险(__wakeup() 只是设置一下数据库连接),攻击者会利用多个类中的魔术方法,像拼积木一样串联起来,形成一条攻击链(POP链)。
- 例如:类A的
__wakeup()方法里调用了$this->obj->execute()。 - 攻击者会构造一个对象,让
$this->obj指向类B的实例,而类B的execute()方法正好能执行系统命令(如system($_GET['cmd']))。 - 这样,当反序列化发生时,A的
__wakeup()被触发 -> 调用B的execute()-> 最终执行了system函数。
完整的攻击流程(漏洞如何产生)
- 入口点(Source):程序存在一个
unserialize($_POST['data'])或unserialize($_COOKIE['user'])之类的代码,且没有对输入进行严格的过滤(比如没有校验allowed_classes)。 - 危险函数(Sink):代码中存在定义了
__destruct()、__wakeup()、__toString()且内部调用了eval()、system()、file_get_contents()、include()等敏感函数的类。 - 构造Payload:攻击者根据源码,手写一个符合序列化格式的字符串,指定需要生成的类名和属性值。
- 触发:程序将恶意字符串反序列化,从而调用魔术方法,执行攻击者的代码。
一个最小化的示例(便于理解)
假设服务器代码如下:
// Dangerous class
class Shell {
public $cmd = 'whoami'; // 默认命令
// 对象被销毁时,自动执行
public function __destruct() {
// 危险函数!
system($this->cmd);
}
}
// 用户可控的输入点(漏洞所在)
$data = $_GET['data']; // 假设用户传入 base64 编码的数据
$obj = unserialize($data);
攻击者构造 Payload:
$evil = new Shell(); // 创建对象
$evil->cmd = 'ipconfig'; // 设置要执行的命令
echo serialize($evil); // 输出:O:5:"Shell":1:{s:3:"cmd";s:8:"ipconfig";}
攻击过程: 攻击者访问 http://victim.com/?data=O:5:%22Shell%22:1:%7Bs:3:%22cmd%22;s:8:%22ipconfig%22;%7D,脚本反序列化这个字符串,生成了 Shell 对象,脚本结束时 __destruct() 被自动调用,执行了 system('ipconfig'),命令注入成功!
总结与防御(为什么会有这个漏洞?)
根本原因:不信任外部数据,反序列化是把“不可信的数据”当成了“可信的对象”来使用。
防御措施(从PHP 7.4+开始):
-
升级PHP:新版PHP对反序列化有更多限制。
-
过滤参数:在
unserialize()的第二个参数中,明确指定允许反序列化的类名。// 仅允许反序列化 User 和 Admin 类 $obj = unserialize($data, ['allowed_classes' => ['User', 'Admin']]);
-
使用JSON替代:如果只是为了传数据,不建议用PHP序列化格式,建议使用
json_encode/json_decode(不会自动触发魔术方法)。 -
代码审计:避免使用
unserialize()直接处理用户输入,如果必须用,务必加上类白名单校验。