parse_str()默认危险是因为省略第二个参数时直接将键值对注入当前作用域,不校验变量是否存在,导致覆盖已有变量(如$config['dbhost']被恶意值替换);它解析raw query string、不自动urldecode,且与register_globals无关,PHP 8.0+仍存在此风险。

parse_str() 为什么默认就是危险的?
它不校验变量是否已存在,直接把字符串里的 key=value 解析成当前作用域的变量。比如:
$config = ['dbhost' => 'localhost'];parse_str('config[dbhost]=127.0.0.1');
此时 $config['dbhost'] 就被覆盖成攻击者指定的地址。
- 默认行为是写入全局作用域,不是只存进数组
- 第二个参数
&$result是可选的,但很多老代码根本没传 - 它解析的是 raw query string,不自动 urldecode,但攻击者可以自己编码绕过过滤
哪些典型场景会踩 parse_str() 的坑?
常见于旧版 CMS、自定义路由解析、配置加载逻辑中。
- 直接解析
$_SERVER['QUERY_STRING']:比如parse_str($_SERVER['QUERY_STRING']);,等于把所有 URL 参数无条件转成变量 - 处理加密/解密后返回的字符串:如
parse_str(mchStrCode($data, 'DECODE')),若解密结果可控,就等同于可控输入 - 在未初始化变量的上下文中使用:比如
$is_admin = false;之后调用parse_str(),?is_admin=1就能覆盖
怎么判断代码里有没有 parse_str() 风险?
重点查三类写法:
- 单参数调用:
parse_str($str)—— 最危险,无隔离 - 用在用户输入路径上:
parse_str($_GET['data'])、parse_str(file_get_contents('php://input')) - 和 extract() 混用:
parse_str($s); extract($arr);,双重覆盖风险叠加
注意:PHP 8.0+ 已弃用 register_globals,但 parse_str() 的变量覆盖行为完全不受影响,它和 ini 设置无关。
立即学习“PHP免费学习笔记(深入)”;
修复时最容易忽略的兼容性细节
- 传入
&$result 后,必须显式从数组取值:parse_str($str, $out); $dbhost = $out['dbhost'] ?? null;
- 不要以为加了
filter_var() 就安全 —— 它只处理值,不阻止键名覆盖(比如 auth[role]=admin 还是能覆盖数组结构)
- 老项目升级时,注意某些框架封装的“安全 parse_str”可能只是简单 trim 或白名单过滤,没禁用方括号语法,仍可构造
config[db][host] 类嵌套覆盖
&$result 后,必须显式从数组取值:parse_str($str, $out); $dbhost = $out['dbhost'] ?? null;
filter_var() 就安全 —— 它只处理值,不阻止键名覆盖(比如 auth[role]=admin 还是能覆盖数组结构)config[db][host] 类嵌套覆盖真正安全的做法,是彻底放弃将用户输入直接喂给 parse_str(),改用 parse_str() 的替代方案:先用 parse_url() + urldecode() 手动拆解,再逐项校验键名白名单。



















