ThinkPHP中直接拼接用户输入到where()字符串、query()和execute()原生SQL语句会触发SQL注入;安全做法是使用数组条件或参数化查询。

ThinkPHP 里哪些写法会直接触发 SQL 注入
不是所有字符串拼接都安全,where()、query()、execute() 这几个方法最容易掉坑。尤其当用户输入直接进 where('id = ' . input('id')) 或 query('SELECT * FROM user WHERE name = "' . input('name') . '"'),PDO 的预处理根本没生效,数据库直接执行拼好的语句。
常见错误现象:SQLSTATE[42000]: Syntax error or access violation 看似是语法错,其实是恶意输入把单引号闭合了,后面跟了 UNION SELECT;或者后台日志里出现 ' OR '1'='1 这类明显测试 payload。
- 用户输入未经过
input()的过滤参数(比如$_GET、$_POST)直接拼进 SQL 字符串,100% 危险 -
where()接数组或闭包是安全的,但接字符串 + 拼接变量就是裸奔 -
Db::name('user')->where('status = 1 AND name LIKE "%' . $keyword . '%"')->select()—— 这种写法哪怕用了 PDO,也完全绕过预处理
为什么用 where() 数组语法比字符串更可靠
ThinkPHP 的数组条件会被框架自动转成参数化查询,底层调用的是 PDO 的 prepare() + bindValue(),变量值永远不会被当 SQL 解析。而字符串条件会走 parseWhere() 解析器,一旦含变量,就退化为字符串替换。
使用场景:查用户、分页筛选、后台搜索框——只要条件字段和值来自用户,一律用数组。
-
where(['username' => input('username'), 'status' => ['in', [0,1]]])✅ 安全,框架生成占位符 -
where('username = "' . input('username') . '"')❌ 危险,引号被绕过,"admin" OR "1"="1"直接生效 - 注意
like场景:用['like', '%' . input('kw') . '%'],别自己拼"%{$kw}%"
query() 和 execute() 怎么写才不踩坑
这两个方法本意就是执行原生 SQL,框架不会帮你做任何参数化处理。想安全,唯一办法是手动传参数组,让 ThinkPHP 走 PDO 绑定流程。否则等于主动打开注入大门。
性能影响:手动传参比字符串拼接多一次解析,但可忽略;兼容性上,所有 ThinkPHP 5.1+ 和 6.x 都支持该用法。
- ✅ 安全写法:
Db::query('SELECT * FROM user WHERE id > ? AND status = ?', [10, 1]) - ✅ 命名参数也行:
Db::query('SELECT * FROM user WHERE id > :min_id', ['min_id' => 10]) - ❌ 危险写法:
Db::query('SELECT * FROM user WHERE id > ' . input('id'))—— 无任何防护 - 注意:参数数组里的值,依然要提前校验类型,比如
id应用intval(),避免传入1 OR 1=1这类数字型注入(虽少见但存在)
自定义查询构造器时容易漏掉的防御点
很多人在封装通用搜索方法、或写 Repository 层时,会把条件组装逻辑抽出去,这时极易忘记参数绑定。比如写了个 buildWhere() 函数返回字符串,再塞给 where(),结果还是拼接。
容易被忽略的地方:关联查询中的 with()、子查询的 buildSql()、以及使用 raw() 构造表达式时——这些地方一旦混入用户输入,且没走绑定流程,就等于开了后门。
- 用
raw()时,只允许用于固定 SQL 片段,如raw('COUNT(*) as total');绝不能写raw('username = "' . $input . '"') - 子查询中若需传参,必须用
where()数组或query()参数数组,不能在子查询字符串里拼 - 第三方扩展(如 think-swoole)若绕过 Db 类直连 PDO,请确认它是否复用 ThinkPHP 的绑定逻辑,否则需自行加固
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











