PAYATHON 2026

phpMyAdmin 为何在 Session 数组键名 PMA_token 两侧添加空格?

数据小许

结论

$_SESSION[' PMA_token '] 两侧的空格是键名的一部分。phpMyAdmin 曾用这种写法防止旧式请求变量导入和 register_globals 环境中的 Session Token 覆盖。空格不会让数组元素变成只读,现代 PHP 项目也不必为了安全采用这种写法。

phpMyAdmin 的历史安全修复将 $_SESSION['PMA_token'] 改成了 $_SESSION[' PMA_token '],同时添加了题目中的注释,并调整了 Session 初始化与全局变量导入的顺序。历史修复提交

PHP 如何处理带空格的数组键?

PHP 数组的字符串键可以包含空格,访问时必须保留这些字符。下面三个键互不相同:

$values = [
    'PMA_token' => 'first',
    ' PMA_token ' => 'second',
    'PMA_token ' => 'third',
];

echo $values['PMA_token'];   // first
echo $values[' PMA_token ']; // second
echo $values['PMA_token '];  // third

带空格的字符串键没有额外保护。使用完全相同的键,仍然可以覆盖原值:

$_SESSION[' PMA_token '] = 'old-value';
$_SESSION[' PMA_token '] = 'new-value';

注释中的“防止覆盖”针对特定的数据进入路径;PHP 代码仍然可以给这个元素重新赋值。

当时要防止哪一种覆盖?

旧版 PHP 的 register_globals 会将外部输入注册为全局变量。结合当时 Session 变量的全局关联机制,请求参数可能影响服务器保存的同名 Session 变量。

phpMyAdmin 的历史问题记录描述过这种情况:开启 register_globals 时,攻击者可能通过请求设置与 Session Token 关联的变量,再提交相同的 token,绕过 CSRF 校验。历史漏洞记录

PHP 会规范化普通请求参数名中的空格、点号等字符,例如:

parse_str('PMA token=example', $input);

echo $input['PMA_token']; // example

直接在 PHP 数组中使用带空格的字符串键则不会经过相同处理。将内部键名改为 ' PMA_token ',可以避开当时普通顶层请求参数经过名称处理后形成的同名覆盖路径。PHP 外部变量说明

这个命名有安全方面的考虑,不能仅用“名字比较少见,降低误用概率”来解释。当年的修复还涉及变量导入顺序等其他改动,也不能把整个漏洞修复归功于这两个空格。

题目中的判断实际做了什么?

if (! empty($_SESSION[' PMA_token '])) {
    return;
}

如果 Session 中已有非空的 Token,这段判断就会返回当前函数,避免继续执行后面的初始化逻辑。

它既不验证客户端提交的 Token,也不阻止后续代码修改 Session。phpMyAdmin 5.2.2 的完整源码中,Token 生成逻辑在这个判断之后才会调用。Session.php 源码

自己的代码是否需要这样写?

新项目通常不需要为了安全给键名加空格。register_globals 已在 PHP 5.4.0 中移除,当年的机制不适用于现代 PHP 环境。PHP 5 更新记录

可以按下面的方式处理:

  1. 使用清晰、稳定的键名,例如 $_SESSION['my_app']['csrf_token'],通过应用或模块层级减少命名冲突。
  2. 明确区分请求输入与服务器维护的 Session 状态。不要把 $_GET、$_POST 或 $_REQUEST 整体合并进 $_SESSION,也不要让客户端决定服务器保存的 CSRF Token。
  3. 维护既有系统时,保留约定的键名。修改 ' PMA_token ' 时,必须同时检查生成、读取、校验和清理位置,不能只删除某一处的空格。

既有接口或存储格式如果要求精确匹配带空格的键名,应保留空格。自行设计新键名时,清晰的命名和受控的写入路径更容易维护。

备注:内容仅供参考。