是的,where()等链式方法默认防注入,但字符串拼接(如where('id=' . input('id')))、误用raw()/exp()、动态字段/表名未白名单校验会绕过预处理。

ThinkPHP 5.1+ 的 where()、select() 等链式方法默认就走 PDO 预处理,只要你不手动拼 SQL,它天然防注入——但绝大多数线上漏洞,恰恰出在“以为安全”的地方。
where() 里哪些写法会绕过预处理?
框架只对「结构化参数」做自动绑定,一旦你把变量直接拼进字符串,预处理就彻底失效。
-
where('id = ' . input('id'))—— 字符串拼接,input('id')是'1 OR 1=1'就直接炸库 -
where("title like '%" . input('q') . "%'")—— 双引号内拼接 + 无占位符,等同裸奔 -
where('status = ' . $status)—— 即使$status来自数据库,若上游没过滤,可能被二次注入 -
order(raw(input('sort')))——raw()是明确告诉框架“别管我”,排序字段必须白名单校验,不能靠它放行
原生 SQL 必须显式绑定的三种场景
复杂子查询、JSON 字段操作、窗口函数等无法用链式方法表达时,Db::query() 或 Db::execute() 必须手动绑定,否则等于放弃防护。
- 用问号占位符:
Db::query('SELECT * FROM user WHERE name = ? AND status = ?', [input('name'), input('status')]) - 用命名占位符:
Db::query('SELECT * FROM user WHERE created_at > :start', ['start' => input('start')]) - 用
bind()分步写(适合长参数):Db::bind(['id' => (int)input('id')])->query('DELETE FROM log WHERE id = :id') - 绝对禁止:
Db::query("SELECT * FROM user WHERE name = '" . input('name') . "'")—— 无论单引号双引号,都错
ThinkPHP 3.x 的 %d/%s 风格还能用吗?
能,但仅限于 ThinkPHP 3.x;5.1+ 已移除该语法,强行混用会导致参数不绑定、SQL 解析失败或静默截断。
立即学习“PHP免费学习笔记(深入)”;
- TP3.x 写法(已废弃):
$Model->where("id=%d AND username='%s'", [$id, $username])->select() - TP5.1+ 等效替代:
Db::name('user')->where(['id' => $id, 'username' => $username])->select()或用数组条件自动绑定 - 注意:
%d强制转整型看似安全,但若字段本应是字符串(如 UUID),类型误判反而导致逻辑错误
最易被忽略的一点:预处理只保「参数」,不保「表名」「字段名」「ORDER BY / GROUP BY 子句」。这些动态部分必须走白名单校验,而不是依赖 raw() 或类型转换来“蒙混过关”。



















