可以明确告诉你:PHP 7.4虽然已经彻底移除了register_globals,但变量覆盖漏洞可没跟着消失。问题的根源,仍然在extract()、parse_str()以及手动的赋值逻辑上。

直接结论:PHP 7.4 已彻底移除 register_globals,但变量覆盖漏洞仍存在,根源在 extract()、parse_str() 和手动赋值逻辑
首先得明确一点:PHP 7.4 不再支持 register_globals(这个配置早在 PHP 5.4 就被废弃,5.6+ 就已经完全删除了)。所以,你不用担心因为 ini 设置错误,导致“自动注册 GET/POST 变量为全局”。但问题在于,开发者自己写的代码,依然可能触发覆盖——比如用 extract($_GET) 把用户输入直接变成变量,或者在循环中反复用 = 覆盖同名变量。
extract() 是最常被忽略的覆盖入口,必须加白名单或禁用
extract() 绝对是个高危操作,很多开发者都在它身上栽过跟头。它会把数组的键名直接转为变量名并赋值,攻击者可以借此轻松覆盖已有变量——比如 $flag、$is_admin 这类关键变量。
- 默认行为极其危险:
extract($_GET)允许你传个?user_id=123&is_admin=1,就能直接覆盖$is_admin的值。 - 千万别用
EXTR_OVERWRITE(默认模式),它会无条件覆盖所有变量。 - 必须显式指定白名单:
extract($_GET, EXTR_IF_EXISTS, ['user_id', 'page', 'sort']),这样只有白名单里的变量才会被处理。 - 更安全的做法,是彻底不用
extract(),改为显式赋值:$user_id = $_GET['user_id'] ?? null;,清晰明了。 - 如果必须批量处理,可以先过滤键名:
$allowed = array_intersect_key($_GET, array_flip(['user_id', 'page'])); extract($allowed);,这样能确保只有你想用的变量被提取出来。
循环内字符串累积时,= 和 .= 的区别决定是否覆盖
这个错误相当常见,尤其是在循环里用 = 赋值,结果只保留了最后一次循环的结果:
- 错误写法:
$out = "$key: $value";→ 每次循环都重置$out,最终只剩最后一项。 - 正确写法:
$out .= "$key: $value\n";→ 累积拼接,但前提是你要先初始化$out = '';。 - 更健壮的做法是避免全局拼接,改用数组收集,最后用
implode()合并:$lines[] = "$key: $value"; echo implode("\n", $lines);,这样既安全又高效。 - 特别注意:如果
$out在函数外定义,又在函数内用global $out修改,那又回到了全局变量污染的老路。正确的做法是参数传入 + 返回值输出。
类属性和常量要靠可见性与类型约束防覆盖,而非信任命名
PHP 7.4 支持带类型和可见性的属性声明,这是防意外覆盖的底层防线,比单纯依赖命名规范要靠谱得多:
- 用
private或protected替代public属性,从根本上阻止外部直接赋值:private string $token; - 常量必须用
public const,且不可修改:public const API_TIMEOUT = 30;(define()是全局的、无命名空间、不支持类型,已经基本淘汰了)。 - 类内部也不应裸写
$this->token = $_GET['t'];,而应走 setter 并校验:public function setToken(string $t): void { if (ctype_alnum($t)) $this->token = $t; },这样能确保数据安全。 - 静态属性若需共享状态,优先用
private static+ 静态方法控制,而不是依赖全局变量。
必须警惕的是,真正难防的不是语法限制,而是开发者在“图省事”时,绕过类型检查、跳过白名单、滥用 extract(),或者在闭包里隐式捕获并修改父作用域变量——这些行为在 PHP 7.4 下依然有效,也依然危险。