PHP反序列化漏洞原理是啥

wen PHP项目 1

本文目录导读:

PHP反序列化漏洞原理是啥

  1. 第一层:什么是序列化与反序列化?
  2. 第二层:危险的“魔术方法”
  3. 第三层:POP链(Property-Oriented Programming,面向属性编程)
  4. 完整的攻击流程(漏洞如何产生)
  5. 一个最小化的示例(便于理解)
  6. 总结与防御(为什么会有这个漏洞?)

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 函数。

完整的攻击流程(漏洞如何产生)

  1. 入口点(Source):程序存在一个 unserialize($_POST['data'])unserialize($_COOKIE['user']) 之类的代码,且没有对输入进行严格的过滤(比如没有校验 allowed_classes)。
  2. 危险函数(Sink):代码中存在定义了 __destruct()__wakeup()__toString() 且内部调用了 eval()system()file_get_contents()include() 等敏感函数的类。
  3. 构造Payload:攻击者根据源码,手写一个符合序列化格式的字符串,指定需要生成的类名和属性值。
  4. 触发:程序将恶意字符串反序列化,从而调用魔术方法,执行攻击者的代码。

一个最小化的示例(便于理解)

假设服务器代码如下:

// 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+开始):

  1. 升级PHP:新版PHP对反序列化有更多限制。

  2. 过滤参数:在unserialize()的第二个参数中,明确指定允许反序列化的类名

    // 仅允许反序列化 User 和 Admin 类
    $obj = unserialize($data, ['allowed_classes' => ['User', 'Admin']]);
  3. 使用JSON替代:如果只是为了传数据,不建议用PHP序列化格式,建议使用 json_encode / json_decode(不会自动触发魔术方法)。

  4. 代码审计:避免使用 unserialize() 直接处理用户输入,如果必须用,务必加上类白名单校验。

抱歉,评论功能暂时关闭!