是的,ThinkPHP 5.1+ 的 where() 等链式方法默认通过 PDO 预处理和参数绑定防 SQL 注入,但字符串拼接(如 where('id=' . input('id')))、误用 raw()/exp()、动态字段/表名未白名单校验等仍会导致注入。

ThinkPHP 的 where() 方法默认是否防 SQL 注入?
是的,只要不手动拼接 SQL 字符串,ThinkPHP 5.1+ 的 where()、find()、select() 等链式方法默认使用 PDO 预处理,参数自动绑定,天然防基础 SQL 注入。
但陷阱在「看似安全实则危险」的写法里:
-
where('id = ' . input('id'))—— 字符串拼接,直接绕过预处理 -
where('status = ' . $status)—— 即使 $status 来自数据库,若未过滤就拼入,也可能被二次注入 -
where(['title' => ['like', '%' . input('q') . '%']])—— 这种写法安全,因为like操作符会触发参数绑定 -
where('title like "%' . input('q') . '%"')—— 错!双引号内拼接 + 无占位符 = 注入温床
哪些 ThinkPHP 方法必须显式用 bind() 或问号占位符?
当你不得不写原生 SQL(比如复杂子查询、窗口函数、JSON 查询等),就必须主动用 query() / execute() + 绑定,否则等于裸奔。
关键点:
立即学习“PHP免费学习笔记(深入)”;
-
Db::query('SELECT * FROM user WHERE name = ? AND status = ?', [input('name'), input('status')])—— 安全,问号占位 + 数组绑定 -
Db::query('SELECT * FROM user WHERE name = :name', ['name' => input('name')])—— 也安全,命名占位符 -
Db::bind(['name' => input('name')])->query('SELECT * FROM user WHERE name = :name')—— 等效,但更易读长参数 - 别用
Db::query("SELECT * FROM user WHERE name = '" . input('name') . "'")—— 不管有没有单引号,都错
ThinkPHP 的 raw() 和 exp() 是什么?为什么它们是高危接口?
raw()(TP6)和已废弃的 exp()(TP5)本质是告诉框架:“这段内容我来负责安全,你别动它”。一旦用了,框架就跳过所有转义和绑定,原样插入 SQL。
常见误用场景:
-
where('created_time > ' . raw(input('start')))—— 如果 input('start') 是'2024-01-01' OR 1=1,就炸了 -
order(raw(input('sort')))—— 排序字段不能参数化,但必须白名单校验,不能靠raw()放行 - 正确替代:
in_array(input('sort'), ['id', 'name', 'create_time']) ? input('sort') : 'id'
记住:raw() 不是“高级用法”,是“最后手段”——只应在确认输入绝对可信(如后台固定配置、枚举值映射)时才用。
除了写法,还有哪些容易被忽略的注入入口?
SQL 注入不止发生在 where,这些地方同样危险:
-
field():如field(input('fields'))—— 字段名无法参数化,必须严格白名单校验 -
table():动态表名(如分表场景)必须用正则限制格式,例如preg_match('/^log_\d{6}$/', $table) - 关联查询中的
alias()或join()表别名 —— 同样不可信输入直传 - JSON 字段查询(如 MySQL 5.7+ 的
->操作符):TP 默认不处理 JSON 路径字符串,where('info->"$.name"', input('name'))是安全的,但where('info->"$. ' . input('key') . '"', ...)就不是
最麻烦的是「多层嵌套 + 动态字段 + 用户可控排序/分组」,这种逻辑往往藏在封装的 BaseService 里,审查时容易漏掉 bind 或白名单。


















