DB::raw不能直接拼接变量,因其绕过参数绑定机制,易导致SQL注入;动态值必须通过bindings传入,SQL结构(如函数、运算符)才可写在raw中。

DB::raw 为什么不能直接拼接变量
因为 DB::raw 会绕过 Laravel 的查询构建器参数绑定机制,直接把字符串塞进 SQL。如果用字符串拼接传入用户输入,比如 DB::raw("name = '$request->name'"),就等于裸奔式 SQL 注入——哪怕你用了 whereRaw 或 selectRaw,只要里面含未过滤的变量,风险一样存在。
正确做法:只在 raw 中留占位符,用 bindings 分离数据
核心原则是:raw 字符串里只写 SQL 结构(函数、运算符、字段名),所有动态值必须通过第二个参数(bindings)传入,由 PDO 底层做预处理绑定。
-
DB::select("SELECT * FROM users WHERE status = ?", ['active'])是安全的,但不涉及raw -
DB::table('users')->whereRaw('created_at > ?', [now()->subDays(7)])->get()才是raw+ bindings 的标准写法 - 如果要用 MySQL 函数或复杂表达式,
raw只负责包裹函数名和语法,例如:DB::raw("DATE_FORMAT(created_at, '%Y-%m')"),它本身不含变量,所以安全 - 需要动态格式化字符串?不要拼进
raw,改用 bindings:比如whereRaw('name LIKE ?', ["%{$search}%"]),而不是DB::raw("name LIKE '%{$search}%'")
哪些场景容易误用 raw 导致漏洞
常见踩坑点集中在「以为 raw 能自动转义」或「混淆了 raw 和普通 where 的能力边界」:
- 用
DB::raw("status = '{$status}'")替代where('status', $status)—— 完全没必要,且危险 - 在
orderByRaw中拼接用户传入的字段名,如orderByRaw("{$sortField} {$sortDir}")—— 字段名无法用 bindings 绑定,必须白名单校验:in_array($sortField, ['name', 'email', 'created_at']) - 在
selectRaw中混入变量,例如selectRaw("CONCAT(first_name, ' ', {$middle})")—— middle 名字应走 bindings,或改用DB::raw("CONCAT(first_name, ' ', ?)")+ 第二个参数数组 - 使用
DB::raw构造 JSON 查询(如 MySQL 5.7+ 的JSON_CONTAINS),却把 JSON 字符串直接拼进去 —— 正确方式是先json_encode($data),再作为 binding 传入:whereRaw('JSON_CONTAINS(meta, ?)', [json_encode(['tag' => 'hot'])])
Bindings 类型和 NULL 处理要特别注意
PDO 默认把 PHP null 绑定为 SQL NULL,这通常没问题;但如果你显式传了字符串 'null' 或空数组,就可能出错。另外,bindings 数组顺序必须严格匹配 ? 占位符出现顺序。
- 多个
?占位符时,bindings 必须是索引数组,不能是关联数组:whereRaw('price BETWEEN ? AND ?', [10, 100])✔️,['min' => 10, 'max' => 100]❌ - 想用命名绑定(如
:min)?Laravel 的whereRaw不支持,得换用DB::select或原生 PDO - 布尔值传入 bindings 会被转成整数(
true → 1,false → 0),MySQL 里一般可接受;但 PostgreSQL 对boolean类型更敏感,建议明确 cast:(bool) $active - 日期时间对象(如
Carbon)传入 bindings 会被自动转成字符串,但要注意时区一致性 —— 最好统一用$date->toDateTimeString()显式控制格式
真正难的不是写对一个 whereRaw,而是每次加 raw 都得问一句:这个字符串里有没有可能来自不可信输入?有没有字段名、表名、排序方向、JSON 片段这类无法用 bindings 表达的内容?这些地方永远需要额外校验,而不是指望 raw 自己扛住。


















