今天将正式掌握代码审计的核心技能——从用户输入点一路追踪到危险函数,验证漏洞是否真实存在。同时,会深入理解 PHP 中几种经典的变量覆盖漏洞,它们常常成为全局变量污染、权限绕过乃至代码执行的根源。
第十二周·周二:变量覆盖漏洞与参数追踪——extract、parse_str、$$ 审计
🎯 今日学习目标(达成效果)
- 能解释变量覆盖漏洞的本质:攻击者通过控制外部输入,修改程序预期之外的变量值,从而改变程序执行逻辑;
- 能识别PHP 中导致变量覆盖的常见函数和特性:
extract()、parse_str()、import_request_variables()(已废弃)、$$可变变量,以及全局变量注册(register_globals,已淘汰但审计旧系统时需留意); - 能独立编写存在变量覆盖缺陷的 PHP 代码,并构造攻击 payload 验证漏洞;
- 能使用 Seay 审计系统 + 手工追踪,在 DVWA 或自建代码中定位变量覆盖点,从参数输入追踪到最终的逻辑判断或危险函数;
- 能总结防御变量覆盖的最佳实践:禁用或安全使用
extract()、严格限定允许覆盖的变量范围、升级 PHP 版本关闭危险特性。
📘 一、变量覆盖:当你的变量不再属于你
变量覆盖是指程序在运行过程中,原本有固定值的变量,被攻击者通过外部输入(如$_GET、$_POST)意外修改。这通常发生在某些 PHP 函数不加限制地将外部数组导入当前符号表时。
典型场景:
- 开发者使用
extract($_POST)将表单数据直接导入为全局变量,覆盖了原本检查权限的$is_admin = false变量。 parse_str()解析 URL 查询字符串并导入变量,如果参数名与已有变量重名,就会覆盖。$$可变变量允许动态构造变量名,如果攻击者能控制变量名,就可能覆盖关键变量。
危害:
- 权限绕过(强制
$admin = true) - 逻辑判断篡改
- 配合其他漏洞(如文件包含)实现代码执行
📘 二、函数详解:extract()、parse_str()、$$
1.extract()—— 批量导入数组为变量
extract($_POST);// 极不安全!如果$_POST包含username=admin&password=123,执行后会生成$username和$password变量。但如果 POST 数据中包含is_admin=1,且原有$is_admin = false,则$is_admin会被覆盖为1,实现权限提升。
安全用法:
- 使用
EXTR_SKIP标志,禁止覆盖已有变量; - 使用
EXTR_PREFIX_ALL为所有导入变量添加前缀(如post_),避免冲突。
2.parse_str()—— 解析查询字符串并导入变量
parse_str($_SERVER['QUERY_STRING'],$output);// 若只传一个参数,直接导入当前符号表!当仅传入第一个参数(字符串)时,该函数会将解析出的变量直接注册到当前作用域,效果等同于extract()。攻击者可通过构造 URL?auth=false覆盖关键的$auth变量。
安全用法:始终使用两个参数,将结果存入数组而不会污染变量空间。
parse_str($_SERVER['QUERY_STRING'],$result);3.$$可变变量 —— 动态变量名的陷阱
$var='page';$$var='index';// 相当于 $page = 'index'如果变量名部分被攻击者控制,可以覆盖任意变量。典型漏洞场景:遍历$_GET或$_POST时使用$$key = $value。
foreach($_POSTas$key=>$value){$$key=$value;// 危险!攻击者可覆盖任何变量}4. 已废弃但需注意的import_request_variables()
在 PHP 4 - 5.3 中,该函数可将 GET/POST/Cookie 变量导入全局作用域,类似register_globals。审计老旧系统时若发现此函数,需立即标记为高危。
📘 三、参数追踪:从入口到危险函数的“溯源”方法
代码审计不仅是发现敏感函数,更重要的是验证攻击者能否控制该函数的参数。这就是参数追踪。
标准审计流程:
- 定位输入源:搜索
$_GET、$_POST、$_REQUEST、$_COOKIE、$_SERVER、$_FILES等。 - 追踪变量传递:沿着变量在代码中的赋值、函数调用、包含文件一路追踪,观察中途是否经历了过滤函数(如
htmlspecialchars、intval、mysql_real_escape_string)或自定义校验。 - 到达危险函数(Sink):如
eval、system、mysql_query、include等。 - 研判:过滤是否充分?是否可绕过?最终确定漏洞是否真实存在。
手工追踪技巧:
- 在 Seay 中,点击某个危险函数,它会自动列出所有引用该函数的位置。但你需要向上翻看函数调用链。
- 在 VSCode 中,用
Ctrl+Shift+F搜索变量名,快速找到所有引用点。 - 使用
var_dump()或debug_backtrace()在动态调试时打印变量值。
✍️ 四、动手实践:审计并利用变量覆盖漏洞
实践 1:审计一段存在extract()漏洞的登录代码
创建login_extract.php:
<?php$is_admin=false;extract($_POST);// 漏洞点!if($username==='admin'&&$password==='secret'){$is_admin=true;}if($is_admin){echo'Welcome admin! Here is your flag.';}else{echo'Login failed.';}?>攻击:
使用 Burp 或 curl 发送 POST 请求:
username=admin&password=secret&is_admin=1由于extract($_POST)会创建$username、$password、$is_admin,后面两者即使密码错误,$is_admin也被强制设为1,导致权限绕过。
防御方案:
- 使用
extract($_POST, EXTR_SKIP);避免覆盖已存在的$is_admin。 - 或者完全避免使用
extract(),直接访问$_POST数组。
实践 2:审计parse_str()无数组参数漏洞
创建parse_vuln.php:
<?php$debug=false;if(isset($_SERVER['QUERY_STRING'])){parse_str($_SERVER['QUERY_STRING']);// 单参数,直接导入变量}if($debug){phpinfo();}?>访问http://localhost/parse_vuln.php?debug=1,$debug被覆盖为1,导致phpinfo()执行,泄露敏感信息。
修复:parse_str($_SERVER['QUERY_STRING'], $output);并仅使用$output数组。
实践 3:$$变量覆盖导致的文件包含
创建dynamic_include.php:
<?php$page='home.php';foreach($_GETas$key=>$value){$$key=$value;// 覆盖 $page}include($page.'.php');?>攻击者访问?page=../../etc/passwd,导致任意文件包含。如果目标文件内容可控(如日志注入),可进一步实现代码执行。
实践 4:使用 Seay 扫描 DVWA 并追踪变量覆盖点
- 在 Seay 中加载 DVWA 源码,点击自动审计。
- 在结果中寻找
extract或parse_str记录。DVWA 老版本中login.php可能使用extract处理 POST 变量,追踪该处是否会影响登录逻辑。 - 定位到具体代码行,结合上下文手动研判。例如某处
extract($_POST)但后续仅使用$_POST数组,实际未造成覆盖——这种属于安全编码,但仍列为审计点。
📝 五、课后测试题与解析
测试题 1:请写出一段存在变量覆盖漏洞的 PHP 代码,并说明攻击者如何利用该漏洞绕过权限验证。
参考答案:
$is_admin=false;parse_str($_SERVER['QUERY_STRING']);if($is_admin){echo'Admin Area';}攻击者访问http://target.com/index.php?is_admin=1,parse_str()将is_admin=1导入当前作用域,覆盖了原有的$is_admin,使其变为1,从而绕过权限检查。
测试题 2:在进行 PHP 代码审计时,如何追踪一个用户输入是否最终流入危险函数?请简述流程。
参考答案:
- 使用 Seay 或 IDE 的全局搜索功能查找所有外部输入源(
$_GET,$_POST等)。 - 选择一个特定的输入点,记录变量名。
- 在代码中追踪该变量的所有赋值和使用位置(通过搜索变量名),查看是否经过过滤函数(如
intval、mysql_real_escape_string、自定义正则)。 - 确认变量是否直接或间接(通过数组、对象)传递给危险函数(
eval、system、mysql_query、include等)。 - 如果变量在未经有效过滤的情况下到达危险函数,且攻击者能完全控制其值,则判定该路径存在漏洞。反之若经过了强制类型转换或强校验,则为误报。
✅ 今日学习效果自检清单
- 我能说出至少三种导致变量覆盖的 PHP 函数/特性
- 我理解了
extract()不同标志(EXTR_SKIP/EXTR_OVERWRITE)的安全影响 - 我独立编写了包含变量覆盖漏洞的代码,并成功利用
- 我使用 Seay 定位了靶场源码中的变量覆盖潜在点,并人工进行了研判
- 我掌握了参数追踪的基本方法:从输入源到危险函数的路径分析
- 我知道防御变量覆盖的最佳实践:避免直接将外部数据导入变量空间、使用安全标志、升级 PHP 版本
⚠️ 阶段避坑重点
- 不要混淆
extract()与compact():后者是创建包含指定变量的数组,无覆盖问题。 - 注意
parse_str()的第二个参数:不加第二个参数直接导入变量极其危险,现代代码应杜绝这种用法。 - 变量覆盖未必立即产生漏洞:需要结合后续的控制流判断。如果覆盖的变量仅用于日志输出,可能无危害,但不能忽视。
- Seay 提示的每一条
extract未必是漏洞:很多框架使用了extract但配合EXTR_SKIP或仅用于视图层且不涉及安全逻辑。审计时需看上下文。 - 避免在循环中使用
$$key = $value:即使没有外部输入,也可能因内部数据污染导致变量覆盖。
明天我们将开始SQL 注入的代码审计专项:追踪未过滤的输入如何流入 SQL 语句,以及如何通过参数化查询彻底修复。今天掌握的参数追踪能力将直接应用于明天的实战分析中,请务必亲自完成所有动手实践。