db::raw()仅作sql结构标记,不执行也不绑定,必须嵌入select()/whereraw()等构建器中;动态值须用占位符+bindings传参,列名/表名等标识符必须白名单校验。

DB::raw() 本身不执行 SQL,也不做任何绑定或转义——它只是个“信任标记”。安全与否,完全取决于你怎么用它、用在哪儿、参数怎么传。
DB::raw() 只能嵌入构建器,不能单独执行
常见错误是把整条 SQL 包进 DB::raw() 然后试图运行:DB::raw("SELECT * FROM users WHERE id = 1")。这不会查出任何数据,只会生成一个 RawExpression 对象,后续没接 select() 或 whereRaw() 就彻底失效。
- 正确位置:只用于
select()、whereRaw()、orderByRaw()、groupByRaw()等构建器方法内部 - 它包裹的内容必须是纯 SQL 结构(函数、运算符、字段别名),不能含用户输入值
- 例如
DB::raw("DATE_FORMAT(created_at, '%Y-%m')")安全;DB::raw("name = '{$name}'")危险
动态值必须走 bindings,不能拼进 raw 字符串
只要 raw 字符串里出现用户可控内容(哪怕只是 LIKE 的模糊前缀),就必须剥离出来,用占位符 + 第二个参数数组传入。
- 错:
->whereRaw("email LIKE '%{$q}%'")—— 直接拼接,注入漏洞 - 对:
->whereRaw("email LIKE ?", ['%' . $q . '%']) - 对:
->whereRaw("JSON_CONTAINS(meta, ?)", [json_encode(['tag' => 'hot'])]) - 注意:bindings 数组顺序必须严格匹配
?出现顺序,PDO 不支持命名绑定(:name)在此处生效
列名/表名/排序字段不能参数化,必须白名单校验
? 占位符只能绑定值(values),不能绑定标识符(identifiers)。所以 DB::raw("table3.ElrA{$year}") 这类写法是高危的——$year 若来自请求,攻击者可传 2023; DROP TABLE users--。
- 禁止运行时拼接列名:永远不要在
DB::raw()字符串里做"col_{$var}"这种操作 - 必须预定义映射或白名单:
$columns = [2022 => 'ElrA2022', 2023 => 'ElrA2023']; $col = $columns[$year] ?? throw new InvalidArgumentException(); - 控制器中可用
validate()强制约束:'year' => 'required|integer|in:2022,2023,2024' - 即使做了
(int)$year,也不能放松校验——类型转换无法阻止其被用于其他上下文(如日志、缓存 key)引发二次风险
容易忽略的细节:IN 子句、日期格式、返回值类型
这些不是 DB::raw() 专属问题,但常在配合使用时暴露隐患。
-
WHERE id IN (?)是非法的:PDO 不支持数组绑定到单个?,得手动拼占位符:"IN (" . implode(',', array_fill(0, count($ids), '?')) . ")" - 日期字段慎用字符串比较:建议先用
Carbon::parse($date)->toDateTimeString()格式化,避免时区差异导致漏查 -
DB::select()返回全是字符串:TINYINT(1)字段返回"1"而非布尔值,$casts和访问器全部失效 - 所有 raw 场景下,
NULL值会由 PDO 自动转为 SQLNULL,但传字符串'null'或空数组会导致意外行为
最常被绕过的其实是「列名白名单」和「IN 子句手动拼占位符」这两处。前者因开发直觉认为“只是个数字”,后者因图省事直接 implode(', ', $ids) 拼进 SQL——这两点在 Laravel 11 中依然没有自动防护机制,得靠人盯住。











