变量覆盖 是一类”看着不起眼、实则能掀翻整个程序”的漏洞:攻击者通过某种机制,把你代码里原本不该被用户改的变量给改掉了——比如把 $isAdmin 改成 true、把 $password 覆盖成空,后果可能是越权、绕过认证、改写配置。
以下仅在授权靶场练习;对他人系统利用变量覆盖属违法。
原理:用户能”注入”变量名
正常思维里,变量是程序员在代码里定义的,用户只能通过”值”来影响程序。但某些语言特性允许用户同时控制”变量名”和”值”,于是就能凭空造出或覆盖一个变量。
常见手法(以 PHP 为主)
1. extract() 一把梭
extract() 把数组的键变成变量名、值变成变量值:
$isAdmin = false;
extract($_GET); // 把 GET 参数变成变量
// 访问 ?isAdmin=1 → 创建了 $isAdmin=1,覆盖了上面的 false!
if ($isAdmin) { /* 进入管理员区 */ }
原本 $isAdmin=false 是写死的,但 extract($_GET) 让用户用 ?isAdmin=1 直接覆盖。这是最经典的变量覆盖。
2. parse_str() 同理
parse_str($_SERVER['QUERY_STRING']); // 把查询串解析成变量
和 extract 一样,会把用户参数变成变量,覆盖已有定义。
3. import_request_variables()(已废弃)
老 PHP 函数,直接把 GET/POST/Cookie 导入全局变量,典型覆盖源,现代已移除。
4. $$ 可变变量
PHP 的 $$ 让变量名本身来自另一个变量:
$key = $_GET['k']; // 用户传 k=isAdmin
$$key = $_GET['v']; // 等于 $isAdmin = $_GET['v']
用户传 ?k=isAdmin&v=1,就覆盖了 $isAdmin。这种写法在”批量赋值”时容易出现。
5. 全局变量注册(register_globals)
极老 PHP 有 register_globals=On,会自动把请求参数注册成全局变量,等于天然变量覆盖。现代已默认关闭,但老代码/老配置仍有。
6. 覆盖配置数组的键
$config = ['debug'=>false];
// 若某处把 $_GET 合并进 $config:
$config = array_merge($config, $_GET);
// ?debug=1 → $config['debug']=true,开启调试模式泄露信息
危害举例
- 绕过认证:覆盖
$loggedIn、$isAdmin直接进后台; - 改配置:覆盖
$config['debug']开调试、覆盖数据库密码; - 改白名单:覆盖
$allowed数组让自己通过校验; - 文件包含配合:覆盖
$page变量指向恶意文件。
变量覆盖常作为”跳板”,和其他漏洞组合放大危害。
靶场实战思路
找变量覆盖,审计时盯这些信号:
- 是否有
extract、parse_str、$$、import_request_variables; - 这些函数的数据源是否含
$_GET/$_POST/$_REQUEST; - 被覆盖的变量之后是否被用于”关键判断”(权限、配置、路径)。
典型 CTF:代码先 extract($_GET),又用某变量做 include,你传对应参数覆盖它,包含自己想要的文件。
防御:别让用户碰变量名
1. 不用危险的批量导入
避免 extract($_GET)、parse_str($query) 这类”把用户输入直接变变量”的写法。需要取参就用 $_GET['xxx'] 显式取,且只取你预期的键。
2. 显式声明、显式取值
$isAdmin = false;
$action = $_GET['action'] ?? ''; // 只取需要的键,不批量
3. 避免 $$ 拼接变量名
非必要不用可变变量;若用,严格白名单限制”允许被覆盖的变量名”。
4. 配置关掉全局注册
确保 register_globals=Off(现代默认),不开 allow_url_include。
5. 关键变量后定义 / 常量保护
把不可被覆盖的值用 const 常量或在”取用户输入之后”再赋值,确保用户覆盖不了。
6. 输入校验后再用
任何来自用户的参数,先校验类型、范围、白名单,再参与逻辑。
更多实战案例:变量覆盖怎么出事
PHP 的 $$ 与 extract:foreach($_GET as $k=>$v) $$k=$v; 会让攻击者用任意参数覆盖任意变量,比如覆盖 $isAdmin=true。extract($_GET) 同理。
parse_str 无第二参数:parse_str($_GET['str']) 会把字符串解析进当前作用域变量,覆盖已有变量。
import_request_variables(已废弃)也会覆盖。
覆盖配置文件/白名单:某系统用 $config = include 'config.php';,后续 extract($_REQUEST) 把 $config 覆盖成攻击者给的数组,导致权限校验失效。
常见坑
- 以为只注册特定前缀安全:
import_request_variables('g')仍覆盖全局变量。 $$循环遍历用户输入:这是变量覆盖的高危写法,应避免。- 覆盖后未重新校验:覆盖发生在校验之前,逻辑就全乱了。
- CTF 常见但生产也中招:很多老 CMS 因此被拿 shell。
进阶:修复
不要 extract 整个 $_GET/$_POST;若必须用,加 EXTR_SKIP 前缀避免覆盖已有变量。避免 $$ 遍历用户输入。关键变量(如 $isAdmin、$uid)在用到前显式赋值,不要依赖外部输入。
小测验
- 问题1:
extract($_GET)风险?答案:用户可覆盖任意已存在变量(变量覆盖)。 - 问题2:安全用 extract 加什么标志?答案:
EXTR_SKIP不覆盖已有变量。 - 问题3:
$$k=$v遍历用户输入危险在哪?答案:攻击者可覆盖任意变量如 $isAdmin。
更多实战案例:extract 与 parse_str 的坑
PHP 里 extract($_GET) 会把每个 GET 参数变成同名变量,攻击者传 ?isAdmin=1 就凭空造出 $isAdmin=1,若后面代码用 $isAdmin 判断权限,直接被提权。parse_str($str) 不传第二个参数时,会把字符串解析进当前作用域的变量,同样能覆盖。import_request_variables('GP') 把 GET/POST 注册成全局变量,也是同类风险。这些”把外部输入批量变成变量”的写法,本质是把控制权交给了用户。
更多实战案例:$$ 变量变量与全局覆盖
foreach($_REQUEST as $k=>$v){ $$k=$v; } 这种”变量变量”写法,让攻击者用任意参数名覆盖任意已存在变量。经典利用:覆盖 $_SESSION、$GLOBALS 相关、覆盖配置文件加载标志让校验被跳过、覆盖 auth/loggedin 等权限变量。在老 CMS(如早期 Discuz、MetInfo 等)里这类写法导致过严重漏洞。
更多实战案例:数组覆盖与类型混淆
现代框架常用 $data = array_merge($_GET, $defaults) 之类合并,若顺序写反(array_merge($data, $_GET))就会被用户输入覆盖默认值。还有把 $_GET['x'] 当数组传入本应固定的配置项,导致白名单失效。类型方面,某些弱类型比较让 0 == 'abc' 为 true,覆盖后配合松散比较绕过判断。
常见坑(补充)
- 以为只注册特定变量名安全:
import_request_variables仍覆盖全局。 - extract 用 EXTR_OVERWRITE 默认:会覆盖已有变量,应 EXTR_SKIP 或前缀。
- 覆盖发生在校验之前:逻辑顺序错,校验形同虚设。
- 数组参数覆盖整个配置:
?config[xxx]=覆盖数组元素。
进阶(补充):修复要点
禁止 extract 整个外部数组;必须用时加 EXTR_SKIP 且加前缀避免冲突;避免 $$ 遍历用户输入;关键变量(权限、身份、配置)显式赋值,不依赖外部;合并数组时把默认值放前面、用户输入放后面且只合并白名单字段;对覆盖后的关键值重新校验。
小测验(补充)
- 问题1:extract($_GET) 最大风险?答案:任意参数变成变量,可覆盖权限变量提权。
- 问题2:extract 安全用法?答案:用 EXTR_SKIP 不覆盖已有,或加前缀。
- 问题3:数组合并顺序为何重要?答案:用户输入放后面才能覆盖,放前面会被默认值盖掉,写反就失控。
更多实战案例:覆盖导致鉴权绕过实战
某系统逻辑:先 extract($_POST) 把提交的用户名密码变成变量,再查库校验,校验通过设 $_SESSION['login']=true。若 extract 发生在校验之前,攻击者在 POST 里加 login=1,extract 直接造出 $_SESSION['login']=true(或覆盖后续判断用的变量),绕过数据库校验直接登录。这类”覆盖发生在关键判断之前”的时序问题,是变量覆盖最致命的形态。
更多实战案例:覆盖配置文件与白名单
系统用 $config = include 'config.php'; 加载配置(含数据库密码、调试开关),后续 extract($_REQUEST) 把 $config 覆盖成攻击者给的数组,于是调试模式被打开、密码被改,后续逻辑全乱。还有白名单数组被 ?whitelist[]=evil 追加元素,导致过滤失效。数组型覆盖比单变量覆盖更隐蔽。
常见坑(再补充)
- 覆盖发生在校验之前:时序错,校验形同虚设。
- 数组参数覆盖整个配置:
?config[xxx]=覆盖数组元素。 - extract 用默认 OVERWRITE:会覆盖已有变量,应 EXTR_SKIP。
- 忽略类型混淆:弱类型比较让覆盖后的值绕过判断。
进阶(再补充):修复清单
禁止 extract 整个外部数组;必须时用 EXTR_SKIP 且加前缀;避免 $$ 遍历用户输入;关键变量(权限、身份、配置)显式赋值,不依赖外部;合并数组时把默认值放前面、用户输入放后面且只合并白名单字段;覆盖后重新校验关键值;用严格比较 === 避免类型混淆。
小测验(再补充)
- 问题1:覆盖为什么能绕过登录?答案:在鉴权判断前覆盖登录标志变量。
- 问题2:数组覆盖怎么发生?答案:
?config[key]=value覆盖数组元素,使白名单失效。 - 问题3:extract 安全用法?答案:EXTR_SKIP 不覆盖已有,或加前缀避免冲突。
这一篇你该记住的
变量覆盖是”用户能控制变量名”,从而覆盖程序里关键变量。PHP 常见源:extract()、parse_str()、import_request_variables()、$$ 可变变量、老 register_globals。危害是绕过认证、改配置、配合包含。防御:禁用批量变量导入、显式取参、避免 $$、关键值用常量或后赋值、输入校验。
变量覆盖是”程序内部被篡改”。下一篇 逻辑漏洞 范围更广——它利用业务规则本身的疏漏,在”合法功能”里做不合法的事,比如越权、支付绕过。