Perl CGI 的 param() 为什么会在列表上下文中注入哈希键?
结论
额外的键会出现在 %user_info 中。重复提交 name 参数,让多个值依次作为姓名、额外键和额外值,就能触发这个问题。
例如:
name=Alice&name=is_admin&name=1
对于题目中的代码,%user_info 会包含 is_admin => '1',尽管请求中没有名为 is_admin 的参数。
这里是向哈希中注入键值对,输入不会作为 Perl 源代码执行。是否进一步造成权限问题,取决于后续代码如何使用这个哈希。
为什么参数值会变成哈希键?
题目引用的警告针对这个调用位置:
my %user_info = (
id => 1,
name => $q->param('name'),
);
param('name') 在列表上下文中返回该参数的所有值,在标量上下文中则返回第一个值。CGI 文档明确说明了这一行为。
=> 不会将右边的表达式限制为单个值。哈希赋值的右侧仍然是一个列表,函数返回的多个元素会在其中展开,Perl 再将相邻的两个元素配成键值对。相关规则见 Perl 的列表与哈希赋值说明。
对于上面的 query string,调用返回的列表是:
('Alice', 'is_admin', '1')
整个赋值相当于:
my %user_info = (
'id', 1,
'name', 'Alice',
'is_admin', '1',
);
按位置配对后,is_admin 成为哈希键。它在 HTTP 请求中是 name 的第二个值,在构造哈希时占据了键的位置。
仅提交 name=Alice&is_admin=1,不会让这段只读取 param('name') 的代码出现相同效果。
如何复现?
在已安装 CGI 模块的 Perl 环境中,可以直接用 query string 初始化对象,无需先搭建 Web 服务:
use strict;
use warnings;
use CGI ();
my $q = CGI->new('name=Alice&name=is_admin&name=1');
my %user_info = (
id => 1,
name => $q->param('name'),
);
for my $key (sort keys %user_info) {
print "$key=$user_info{$key}\n";
}
根据上述配对规则,打印结果应为:
id=1
is_admin=1
name=Alice
CGI.pm 还可能输出列表上下文安全警告。该警告用于提示风险,不会自动阻止列表返回。
如果通过 HTTP 复现,GET 请求的查询部分可以写成:
?name=Alice&name=is_admin&name=1
使用 Content-Type: application/x-www-form-urlencoded 的 POST 请求时,请求体可以使用同一串数据:
name=Alice&name=is_admin&name=1
能否覆盖已有的键?
可以,前提是注入的同名键值对出现在原有键值对之后。哈希赋值时,后出现的同名键会覆盖前面的值。
例如,将请求改为:
name=Alice&name=id&name=999
原来的赋值就相当于:
my %user_info = (
id => 1,
name => 'Alice',
id => '999',
);
最终 id 对应的是 '999'。即使某个字段最初由程序固定赋值,也可能被后面展开的输入覆盖。
如何修复?
如果 name 只需要一个值,可以显式使用 scalar:
my %user_info = (
id => 1,
name => scalar $q->param('name'),
);
也可以先赋给标量变量,再构造哈希:
my $name = $q->param('name');
my %user_info = (
id => 1,
name => $name,
);
这两种写法都让 param('name') 处于标量上下文。对于示例请求,只有 'Alice' 会进入 name 字段。
如果业务允许多个姓名,应显式读取多个值,并将数组引用放入哈希:
my @names = $q->multi_param('name');
my %user_info = (
id => 1,
names => \@names,
);
数组引用在外层列表中只占一个元素,因此不会把姓名列表展开为额外的键值对。
直接把原代码中的 param() 换成 multi_param() 仍会返回列表,无法修复这里的哈希赋值问题。关闭警告也不会改变这一行为。scalar 控制返回值数量,字段格式校验、是否拒绝重复参数和权限判断仍需按业务要求处理。
备注:内容仅供参考。
