ThinkPHP 6.0 预处理防护仅在传数组、闭包或安全方法时生效;字符串拼接、原生SQL未用?占位、order/group/having传用户输入、like手动拼%均导致注入。

ThinkPHP 6.0 默认启用 PDO 预处理,但只要你在 where()、query()、execute() 或 order() 里手动拼接用户输入,防护就完全失效——不是框架没防,是你绕过去了。
where() 传字符串 vs 传数组:安全边界在这里
传字符串时,ThinkPHP 不做任何参数绑定,直接解析为 SQL 片段;传数组或闭包,才走预处理流程。很多开发者误以为加了单引号就安全,比如 where("name = '{$_GET['name']}'"),其实 PHP 在双引号内照样解析变量,攻击者输 admin' OR '1'='1 就能闭合引号并注入。
-
where(['name' => input('name'), 'status' => 1])✅ 自动转成WHERE name = ? AND status = ?,值绑定不参与 SQL 解析 -
where('name', input('name'))✅ 等价于数组写法,底层统一走绑定 -
where("name = '" . input('name') . "'")❌ 即使 input() 做了基础过滤,也无法阻止引号逃逸 -
where(function ($q) { $q->where('name', input('name')); })✅ 闭包内仍用安全语法,推荐用于复杂条件
原生 SQL 必须用 ? 占位符,且只支持顺序绑定
Db::query() 和 Db::execute() 本身不解析变量,只认 ? 或命名参数(如 :id),但命名参数在 ThinkPHP 6.0 中需显式开启配置,否则会报错;绝大多数项目用默认配置,所以更稳妥的是坚持问号 + 数组顺序绑定。
-
Db::query("SELECT * FROM user WHERE id > ? AND type = ?", [10, 'admin'])✅ 安全、兼容所有 6.x 版本 -
Db::query("SELECT * FROM user WHERE id > :min_id", ['min_id' => 10])⚠️ 需确认'params_bind' => true已启用,否则不生效 -
Db::query("SELECT * FROM {$table} WHERE id = " . $id)❌ 表名和值都不可信,双重风险 -
Db::execute("INSERT INTO log VALUES ('" . date('Y-m-d') . "', '" . input('msg') . "')")❌ 拼接即裸奔
order()、group()、having() 接用户输入字段时必须白名单校验
这些方法不支持参数绑定,因为字段名/关键字不能当数据传给 PDO。你不能写 order('create_time ' . input('sort')),哪怕 input('sort') 是 DESC,也可能被换成 DESC, (SELECT password FROM admin)。
立即学习“PHP免费学习笔记(深入)”;
$sort = in_array(input('sort'), ['id', 'create_time', 'status']) ? input('sort') : 'id';order($sort . ' ' . (input('order') === 'desc' ? 'DESC' : 'ASC'))-
having('COUNT(*) > ? ', [$min_count])✅ having 的值部分可用问号绑定,但字段名不行 -
orderRaw(input('sort'))❌orderRaw()直接输出到 SQL,等同于关闭防护
like 查询别自己拼 %,用数组语法或 like() 方法
写 where("name LIKE '%{$kw}%'") 是最常见也最危险的模糊查询写法——通配符放大注入效果,$kw 里塞 %' OR '1'='1 就能绕过整个 WHERE 条件。
-
where('name', 'like', '%' . input('kw') . '%')✅ 框架识别like类型,自动绑定 -
where(['name' => ['like', '%' . input('kw') . '%']])✅ 同上,更明确 -
where('name', ['like', input('kw')])✅ 框架自动补前后 %,更简洁 -
where("name LIKE '%" . addslashes(input('kw')) . "%")❌addslashes()对多字节编码、宽字符等场景无效,且破坏原始语义
真正容易被忽略的点是:字段名、表名、排序方向、分组字段这些“SQL 结构部分”,永远不能来自用户直输;哪怕看起来只是几个字母,也要进白名单兜底——因为一旦漏掉一个 in_array() 判断,整个查询构造器的安全模型就塌了。



















