Db::raw()是SQL注入高危区,因其绕过参数绑定与转义,直接将用户输入拼入SQL;必须仅用于可信字面量,动态内容须经白名单校验或改用参数化查询。

Db::raw() 为什么是注入高危区
因为 Db::raw() 的设计目的就是“原样插入”,框架不会对它做任何参数绑定或转义——它等于告诉 ThinkPHP:“这段内容你别管,直接塞进 SQL”。一旦你把用户输入拼进 Db::raw(),就等于手动打开注入大门。
常见错误现象:用 Db::raw('COUNT(' . input('field') . ')'),攻击者传 field=id) OR 1=1 --,最终生成 COUNT(id) OR 1=1 --),破坏语义甚至拖垮查询。
- 动态字段名、函数名、排序方向(
ASC/DESC)、表别名,只要来自不可信输入,就不能进Db::raw() -
Db::raw()适合写死的常量表达式,比如Db::raw('NOW()')、Db::raw('JSON_EXTRACT(data, "$.name")') - 若必须动态,先走白名单校验:
in_array($field, ['id', 'name', 'status']) ? Db::raw($field) : throw new Exception('非法字段')
where() 里混用 Db::raw() 的典型翻车点
很多人以为 “用了 where() 就安全”,结果在数组结构里偷偷塞 Db::raw(),瞬间绕过所有预处理机制。
危险写法:where(['score' => ['>=', Db::raw(input('min_score'))]]) —— input('min_score') 是字符串,Db::raw() 直接把它当 SQL 片段执行,没绑定、不转义。
立即学习“PHP免费学习笔记(深入)”;
- 正确替代方案一(数值强转):
where('score', '>=', (int)input('min_score')) - 正确替代方案二(白名单+raw):
$ops = ['gt', 'lt', 'eq']; $op = in_array(input('op'), $ops) ? input('op') : 'eq'; where('score', $op, (int)input('val')) - 绝对禁止:
where('status', Db::raw(input('sort'))) —— 排序字段不能用 raw,要用 order() + 白名单
Db::query() 配合 Db::raw() 的绑定陷阱
有人试图用 Db::raw() 拼原生 SQL,再丢给 Db::query(),指望后者自动绑定——这是错觉。Db::query() 只识别问号或命名占位符,对 Db::raw() 返回的对象完全无视。
错误示例:Db::query("SELECT * FROM user WHERE id IN " . Db::raw('(' . implode(',', input('ids')) . ')')) —— input('ids') 是数组?字符串?都不重要,Db::raw() 已经把它变成裸 SQL。
- IN 子句安全写法:
$ids = array_map('intval', input('ids', [])); $placeholders = str_repeat('?,', count($ids) - 1) . '?'; Db::query("SELECT * FROM user WHERE id IN ($placeholders)", $ids) - 如果非要用
Db::raw()构建复杂表达式,确保它只包裹**完全可信的字面量**,比如硬编码的字段别名Db::raw('u.id AS user_id') - Docker 或 MySQL 5.6 环境下,检查 PDO 是否禁用模拟预处理:
Db::connect()->getPdo()->getAttribute(PDO::ATTR_EMULATE_PREPARES) === false,否则问号绑定也可能退化为拼接
exp 表达式与 Db::raw() 的等价风险
['exp', '...'] 和 Db::raw() 在防注入层面完全等价,都是“放行模式”。框架文档里说 exp 是“用于复杂表达式”,但没强调它等于关闭安全阀。
比如:where(['title' => ['exp', "LIKE '%" . input('q') . "%"]]) —— 这和 where("title LIKE '%" . input('q') . "%") 一样危险,只是换了个写法。
- 真正安全的
exp用法:where(['score' => ['exp', 'score * 1.2 > 100']]),右侧是固定表达式,无用户输入 - 带用户输入?改用参数化结构:
where('title', 'like', '%' . input('q') . '%'),这个会触发自动绑定 - 所有含
exp或Db::raw()的地方,建议加代码注释说明“此处已校验/白名单控制”,方便后续审计
实际项目里,最易被忽略的是:Db::raw() 不是“高级功能”,而是“逃逸开关”。它存在的意义是兼容特殊语法,不是为了让你绕过安全机制。只要出现用户输入 → 字符串拼接 → Db::raw() 这条链,就必须打断。



















