thinkphp不自动拦截sql注入,安全依赖正确使用pdo参数绑定:where传数组或闭包、原生sql用占位符绑定、动态标识符需白名单校验、禁用raw等高危方法并关闭模拟预处理。

ThinkPHP本身不自动拦截SQL注入,安全完全取决于你写查询的方式。只要用户输入参与了SQL字符串拼接,哪怕只有一处,就可能被利用。
用数组或闭包代替字符串条件
where() 方法只有传数组或闭包时才会触发PDO参数绑定;传字符串则直接走文本替换,毫无防护。
- ✅ 安全写法:
where(['username' => input('username'), 'status' => 1])或where('id', input('id')) - ✅ 复杂逻辑用闭包:
where(function ($q) { $q->where('name', 'like', '%' . input('kw') . '%')->where('status', 1); }) - ❌ 危险写法:
where("username = '" . input('username') . "'")、where("id = " . input('id')) - ⚠️ like不能手拼:
['like', '%' . input('kw') . '%']✅,"%{$kw}%"❌
原生SQL必须显式绑定参数
Db::query() 和 Db::execute() 不做任何自动过滤,必须人工传参数数组,并确保占位符与值严格对应。
- ✅ 位置占位(推荐):
Db::query("SELECT * FROM user WHERE id = ? AND type = ?", [$id, $type]) - ✅ 命名占位(需确认开启):
Db::query("SELECT * FROM user WHERE name = :name", [':name' => input('name')]),前提是配置中启用了params_bind - ❌ 混用失效:
Db::query("", [1, ':name' => 'a'])中的:name不会被识别 - ❌ 禁止拼接:
Db::query("SELECT * FROM user WHERE id = " . input('id'))直接裸奔
动态字段和排序必须白名单校验
表名、字段名、ORDER BY、GROUP BY 等标识符无法用 ? 占位,PDO 绑定只支持值,不支持结构。必须靠硬性校验。
- ✅ 排序字段校验:
$sort = in_array(input('sort'), ['id', 'create_time', 'status']) ? input('sort') : 'id'; order($sort . ' ' . (input('order') === 'desc' ? 'DESC' : 'ASC')) - ✅ 表名校验:
in_array($table, ['user', 'order', 'log']) ?: die('非法表名'); - ❌ 禁止:
order(input('sort') . ' ' . input('dir'))、"SELECT * FROM {$table}" - ⚠️ IN 查询别直接传数组:
where('id', 'in', $ids)✅(框架内部已处理),bind($ids)❌(会转成单个字符串)
禁用危险接口并验证绑定是否真实生效
raw()、exp()、whereRaw() 等接口绕过所有绑定逻辑;模拟预处理(emulate prepares)会让绑定退化为字符串拼接,尤其在Docker或旧MySQL环境中极易失效。
- ✅ 关闭模拟预处理:数据库配置中明确设
PDO::ATTR_EMULATE_PREPARES => false - ✅ 禁用高危方法:通过中间件拦截
raw、exp、whereRaw调用 - ✅ 验证绑定真实发生:开启 debug +
sql_explain,查看日志中是否出现Binding: [value],而非仅SQL: WHERE id = ? - ❌ 不要依赖 input() 的默认过滤:它不做类型约束,也不防引号逃逸,不能替代绑定
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











