thinkphp 8.0防sql注入核心是“值必须绑定,结构必须白名单”:where传字符串不防注入,须用数组触发pdo绑定;order by等语法结构须白名单校验;禁用raw()/exp()及未bind的原生query。

ThinkPHP 8.0 防 SQL 注入的核心原则是:**值必须绑定,结构必须白名单**。框架不会自动防护所有场景,很多看似安全的写法实际仍会触发拼接执行。
用数组或三元语法写 where 条件
字符串拼接的 where 语句(如 where("id = " . input('id')))直接拼进 SQL,不走预处理,等于裸奔。
- ✅ 安全写法:使用键值数组,自动触发 PDO 绑定
where(['id' => input('id')])或where('status', 'eq', $status) - ✅ 闭包内也要遵守规则:
where(function($q) { $q->where('id', input('id')); }) - ❌ 危险写法:
where("name = '" . input('name') . "'")、where("title LIKE '%" . input('q') . "%')
ORDER BY、GROUP BY、表名、字段名必须白名单校验
这些属于 SQL 语法结构,不能参数化。PDO 不允许在 ORDER BY ? 中用占位符传字段名,强行使用会报错 SQLSTATE[HY093]。
- 只允许预设字段:
$allowed = ['id', 'title', 'created_at', 'score'];
用in_array($_GET['sort'] ?? '', $allowed)判断 - 排序方向只接受
ASC或DESC,其他一律 fallback:$order = in_array($_GET['order'] ?? '', ['ASC','DESC']) ? $_GET['order'] : 'DESC'; - 若字段来自配置表等非硬编码来源,额外加标识符正则:
preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $sort)
禁用 raw()、exp() 和原生 query 拼接
raw() 和 exp() 是绕过所有防护的“免死金牌”,一旦使用,框架不再做任何转义或绑定。
- ❌ 绝对禁止:
where('status = ' . raw(input('s')))、order(raw(input('sort')))、Db::query("SELECT * FROM user WHERE name = '" . input('n') . "'") - ✅ 原生查询必须显式 bind:
Db::query("SELECT * FROM user WHERE name = ? AND status = ?", [input('n'), 1]) - ✅ IN 子句要动态生成占位符:
$ids = [1,2,3]; $ph = str_repeat('?,', count($ids)-1) . '?'; Db::query("WHERE id IN ($ph)", $ids);
验证是否真走了绑定,别信日志表面
开启调试后日志显示 SQL: SELECT * FROM x WHERE id = ? 并不代表真的绑定了——这只是 ThinkPHP 的美化输出。
- 开启 MySQL general_log,看真实执行的是
WHERE id = 123还是WHERE id = ? - 在
think\db\Connection::execute()中断点,确认调用了$pdoStatement->bindValue()而非直接execute($params) - 检查 PDO 配置:
PDO::ATTR_EMULATE_PREPARES必须为false,否则低版本 MySQL 会模拟预处理,实际仍是拼接
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











