ThinkPHP5.1动态字段查询须严格白名单校验,禁止拼接用户输入;所有order/group/field/table等参数必须先校验是否在预定义字段白名单中,再使用;raw()和exp模式需格外谨慎,批量操作字段名同样需过滤。

ThinkPHP5.1中动态字段查询(如 order、group、field、table 等接受字段名/表名的参数)无法通过预处理绑定防护,因为SQL语法要求字段名必须是标识符而非参数值。这类操作一旦拼接用户输入,就直接触发注入。防住的关键不是“过滤内容”,而是“限制来源”——只允许白名单内的合法字段名参与构造。
字段名必须走白名单校验
所有由用户控制的字段名(如排序字段 input('sort')、分组字段 input('group')、查询字段 input('fields')),都不可直接透传给 order() 或 field() 方法。
- 定义明确的字段白名单:例如
$allowFields = ['id', 'name', 'email', 'create_time', 'status'] - 校验后才使用:
$sort = in_array(input('sort'), $allowFields) ? input('sort') : 'id'; Db::table('user')->order($sort . ' DESC')->select(); - 多字段支持时用
array_intersect()过滤:$fields = array_intersect(input('fields/a', []), $allowFields); Db::field($fields)->select();
避免在 order/group/table 中拼接用户输入
以下写法全部高危,框架不会拦截,也不支持绑定:
-
order(input('sort') . ' ' . input('order'))—— 字段+方向全由用户控制 -
table('user_' . input('tenant_id'))—— 表名拼接,可切换到任意表 -
group(input('group_by'))—— 可注入user_id, (SELECT password FROM admin)
正确做法是把业务逻辑和字段映射解耦:前端传语义化标识(如 sort=nickname),后端查表映射为真实字段('nickname' => 'username'),再校验是否在白名单中。
立即学习“PHP免费学习笔记(深入)”;
慎用 raw() 和 where(..., 'exp', ...) 处理字段逻辑
Db::raw() 和 where('field', 'exp', ...) 会跳过所有安全机制,把字符串原样塞入SQL。即使你只是想动态加个函数包裹,也必须先确认字段名已白名单校验。
- 危险:
where('name', 'exp', 'UPPER("' . input('q') . '")')——input('q')是用户可控字符串 - 安全:
$q = input('q/s'); where('name', 'exp', "UPPER('$q')")—— 先强制字符串过滤,再单引号包裹(仅限简单场景) - 更安全:
where('name', 'like', '%' . $q . '%')—— 交给框架自动绑定
批量操作中字段名同样需隔离
插入或更新时若允许用户指定字段(如 JSON 提交 {"field": "status", "value": "1"}),不能直接用于 update([$field => $value])。
- 提取键名并校验:
$data = input('post.'); $safeData = []; foreach ($data as $k => $v) { if (in_array($k, $allowFields)) $safeData[$k] = $v; } - 禁用
['exp', ...]结构:旧版 TP5.0 存在parseData()绕过漏洞,TP5.1 虽修复但仍建议禁用该模式,不接受用户传['status' => ['exp', 'NOW()']] - 固定字段优先:能用
['name' => $name, 'email' => $email]就不用动态键名



















