PHP处理用户输入意外数组的全局转换方案可行性问询
我近期发现,若用户提交的变量传入了预期之外的数组(而非字符串类型),可能会引发致命错误或其他非预期行为,示例如下:
现有如下数组:
$list = array( "a" => "first", "b" => "second" );
用户传入的$_REQUEST["key"]会被用于在上述列表中查询对应元素:
echo ($list[$_REQUEST["key"]] ?? null);
如果$_REQUEST["key"]的类型为string、int、float、bool或null,脚本会输出匹配到的条目或无输出(即null),这是符合预期的行为。
如果$_REQUEST["key"]是array类型,脚本会因致命错误终止运行。
最直观的解决方案是在全量代码中添加大量类型校验(使用is_scalar()或!is_array()),但我想从安全角度确认如下替代方案是否合理:
在每个请求初始化阶段就运行以下脚本:
$_COOKIE = array_map(function($e) { return (is_array($e) ? json_encode($e, JSON_INVALID_UTF8_IGNORE) : $e); }, $_COOKIE); $_REQUEST = array_map(function($e) { return (is_array($e) ? json_encode($e, JSON_INVALID_UTF8_IGNORE) : $e); }, $_REQUEST); $_POST = array_map(function($e) { return (is_array($e) ? json_encode($e, JSON_INVALID_UTF8_IGNORE) : $e); }, $_POST); $_GET = array_map(function($e) { return (is_array($e) ? json_encode($e, JSON_INVALID_UTF8_IGNORE) : $e); }, $_GET);
该方案本质上禁用了向服务端提交数组的能力,若代码中某处确实需要接收数组,可手动通过json_decode()解码获取。请问该方案从安全与工程实践角度是否合理可行?
不建议你这么做,不管是安全防护效果还是实际工程落地都有明显问题:
- 首先会直接破坏正常业务逻辑。PHP原生支持URL、表单传数组参数是很多年的标准特性,像多选表单、批量操作接口、多维度筛选参数都靠这个能力实现。你上来全局把超全局变量第一层的数组全转成JSON字符串,所有依赖原生数组传参的代码直接就跑错了。就算你打算后面手动解码,也根本分不清一个JSON格式的字符串是用户正常传的内容,还是你之前转出来的数组,很容易出逻辑bug。
- 其次防护根本不彻底,还可能留安全隐患。你现在写的
array_map只处理超全局变量的第一层,嵌套数组根本碰不到;要是改成递归处理所有层级的数组,对正常逻辑的破坏会更严重。而且这种悄悄把非法输入转成字符串的处理方式,相当于把异常的攻击载荷伪装成了普通字符串,反而可能绕过WAF、代码里的常规过滤规则,比直接报致命错还危险。 - 最后是可维护性和兼容性极差。绝大多数PHP框架、第三方Composer包都默认超全局变量是PHP原生的结构,你全局改了参数结构,会导致第三方依赖出一堆莫名其妙的问题,排查都不好排查。后续接手项目的开发要是不知道你加了这层全局转换,写代码的时候十有八九会踩坑。
正确的处理方式根本不需要这么粗暴:
你碰到的问题本质是「拿用户可控的输入当数组键用的时候,没校验是不是标量类型」,根本不需要全局改参数,只需要在用用户输入当数组键、走敏感逻辑的地方加个is_scalar()判断就行,碰到非预期的数组参数直接返回400拦截,别悄悄把非法输入改头换面放过去。
要是嫌每次写校验麻烦,就封装个统一的请求处理类,取参数的时候明确指定类型:要字符串就走字符串获取方法,方法内部自动做类型校验,不符合类型就返回默认值或者抛参数错误;要数组就走专门的数组获取方法,从根源上避免类型不匹配的问题。
别为了省几行校验代码就全局改语言原生的变量结构,这种藏在请求初始化阶段的隐式逻辑,后面出问题排查的成本,比你省下来的那点代码量高多了。
内容的提问来源于stack exchange,提问作者shuunenkinenbi

