ThinkPHP 6.0原生SQL查询不自动防SQL注入,值须用占位符绑定,结构部分(表名、字段名等)必须白名单校验;查数据用Db::query(),改数据用Db::execute();禁用字符串拼接、Db::raw、exp模式等绕过绑定的写法。

ThinkPHP 6.0 原生 SQL 查询本身不自动防御 SQL 注入,安全完全取决于你如何写、怎么传参。核心原则只有一条:**值必须用占位符绑定,结构部分(表名、字段名、排序方向、GROUP BY 字段等)必须白名单校验,绝不拼接**。
严格区分 query 和 execute
查数据用 Db::query(),改数据用 Db::execute()。混用会掩盖问题——比如用 query 执行 DELETE,返回空数组,但数据已被删,极易误判为“没执行”。
- Db::query('SELECT * FROM user WHERE status = ? AND id > ?', [1, 100]) ✅ 返回二维数组,值绑定防注入
- Db::execute('UPDATE user SET name = ? WHERE id = ?', ['张三', 123]) ✅ 返回影响行数,值绑定生效
- Db::query("SELECT * FROM user WHERE id = " . input('id')) ❌ 表名+值全裸奔,高危
占位符只保“值”,不保“结构”
问号(?)或命名参数(:field)只能保护传入的变量值,对 SQL 结构部分完全无效。ORDER BY、GROUP BY、表名、字段名、HAVING 条件里的关键字,PDO 不接受参数化,必须提前过滤。
- 错误写法:order(input('sort') . ' DESC') → 攻击者输
id; DROP TABLE user--就执行成功 - 正确做法:定义白名单
$allowed = ['id', 'name', 'create_time'];,再判断in_array($sort, $allowed),最后拼接"ORDER BY {$sort} DESC" - 表名动态时同样处理:
if (!in_array($table, ['user', 'order', 'log'])) { throw new Exception(); }
禁用所有绕过绑定的写法
以下写法会直接跳过参数绑定机制,把用户输入原样塞进 SQL,属于高危红线,一律禁止:
立即学习“PHP免费学习笔记(深入)”;
-
Db::raw("status = " . input('s'))→ raw 内容不解析、不绑定 -
where('price', 'exp', 'price > 100 AND uid = ' . input('uid'))→ exp 模式等于手动拼 SQL -
where([input('key') => input('val')])→ 数组键名未校验,可注入字段名 -
where("id = " . input('id'))→ 字符串拼接,无论单引号双引号都无效
子查询与 LIKE 模糊匹配要手动处理
原生 query/execute 不解析嵌套结构,子查询需自己写全或用 selectSub() 构建;LIKE 的通配符必须由 PHP 拼好再传入占位符,不能写在 SQL 字符串里。
- 模糊搜索正确写法:
Db::query("SELECT * FROM user WHERE name LIKE ?", ['%' . $kw . '%']) - 子查询推荐用构造器:
Db::table(Db::raw('(SELECT * FROM log WHERE status = 1) a'))->where(...)->select() - 避免手写嵌套:
Db::query("SELECT * FROM user WHERE id IN (SELECT uid FROM log WHERE type = ?)", [$type])→ 虽然值绑定有效,但嵌套逻辑复杂时易出错,优先走 selectSub



















