<p>ThinkPHP 6.0 默认不自动防御 SQL 注入,安全取决于调用方式:仅传数组、闭包或问号占位符才触发 PDO 预处理;传字符串即裸奔,如 where("name = '" . input('name') . "'") 无法阻止引号逃逸,必须用 where(['name' => input('name')]) 或 Db::query("SELECT * FROM user WHERE id = ?", [$id])。</p>

ThinkPHP 6.0 默认不自动防御 SQL 注入,安全与否完全取决于你调用方式——传数组、闭包或问号占位符才触发 PDO 预处理;传字符串就是裸奔。
where() 传字符串 vs 传数组:安全开关在这里
传字符串时,where() 不做任何绑定,直接拼进 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),但命名参数在 TP6.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)。
$sort = in_array(input('sort'), ['id', 'create_time', 'status']) ? input('sort') : 'id';order($sort . ' ' . (input('order') === 'desc' ? 'DESC' : 'ASC'))
in 查询的坑比你想的深:版本、strict 配置、元素类型都得管
where(['id' => ['in', $ids]]) 不是银弹。TP5.0.23 之前,若 $ids 含字符串表达式如 "1 or 1=1",且未启用 strict 模式,框架可能跳过类型转换,直接拼成 WHERE id IN (1, '1 or 1=1'),触发布尔盲注。
- 输入层强校验:
array_map('intval', input('ids/a')),再剔除零值(注意业务上 id 是否允许为 0) - 框架层约束:TP6+ 数据库配置中启用
'strict' => true,强制所有in参数走 PDO 绑定 - 逻辑层兜底:
where('id in (' . str_repeat('?,', count($ids)-1) . '?)', $ids),手动构造占位符串 + 数组绑定 - 架构层熔断:对超长
$ids(如 >1000 项)触发分页查询或 Redis 缓存 ID 集合
真正危险的不是没写绑定,而是写了却误以为“框架会兜底”;strict 关不开启、表名列名不做白名单、in 数组不校验元素类型——这些点一旦漏掉一个,参数化就形同虚设。


















