Laravel 项目里,大部分常规查询天生就比较安全,但这并不代表项目不会出现 SQL 注入。本文重点回答三个实际问题:哪些写法最危险、如何把现有代码改成安全版本、动态字段和原生 SQL 该怎么处理。
如果你正在排查接口查询、搜索、排序或后台筛选功能,这篇内容可以直接作为检查清单使用。
Laravel 默认安全到什么程度
只要没有手动把用户输入拼进 SQL 字符串,Laravel 默认提供的查询构造器和 ORM 通常是安全的。真正需要警惕的,是主动绕过这层保护的写法,例如 whereRaw()、selectRaw()、orderByRaw(),以及直接使用 DB::select() 拼接 SQL。
问题的关键不在于有没有用 Laravel,而在于用户输入是否被当作 SQL 结构的一部分执行。一旦直接拼接进去,单引号、UNION SELECT、SLEEP(3) 这类注入载荷就有机会生效。
`whereRaw()` 为什么容易出问题
危险点在哪里
whereRaw() 本身不是不能用,问题在于很多项目只写了 SQL 片段,却没有传第二个参数绑定变量。这样等于把用户输入直接交给数据库执行。

$query->whereRaw("name like '%$keyword%'")
上面这类写法里,$keyword 是用户可控值,完全暴露。
安全改法是什么
正确方式是使用参数绑定,把用户输入作为“值”传给占位符,而不是拼到 SQL 文本中。
$query->whereRaw("name like ", ["%{$keyword}%"])
$query->whereRaw("name like concat('%', , '%')", [$keyword])
这两种写法都可以。这里要注意,concat() 本身不会破坏参数化,但只要你又回到 PHP 字符串插值,保护就失效了。
concat('%', '$keyword', '%')
像这样把变量直接插进字符串,看起来只是换了一种写法,实际已经脱离参数绑定。
动态列名和排序字段为什么不能参数化
很多开发者会在搜索、排序、报表导出里动态切换字段,这里是 Laravel 项目里最常见、也最容易被误判的一类风险。
要点很明确:字段名、表名、排序字段不能参数化。原因不是 Laravel 限制,而是 SQL 标准和 PDO 的工作方式决定了占位符只能绑定“值”,不能绑定 SQL 语法结构。
下面这类代码就有风险:
DB::table('products')->select("product_variant_{$request->input('id')}")
如果用户传入 1 FROM users --,就可能触发异常,严重时还可能被进一步利用。
正确处理方式
这类场景不要尝试“参数化字段名”,而是直接做白名单校验或映射。
in_array($field, ['id', 'name', 'created_at'], true)
$columns = [1 => 'product_variant_1', 2 => 'product_variant_2']
如果请求参数本身就应该受限,最好把验证前置到请求层:
$request->validate(['sort' => 'required|in:id,name,created_at'])
这比在查询阶段临时兜底更稳,也更容易维护。
`DB::select()` 和原生查询该怎么写
一旦绕过 ORM 和查询构造器,Laravel 不会自动替你处理所有转义问题。像 DB::select()、DB::statement() 这样的原生接口,是否安全基本取决于你有没有老老实实写占位符。
高危写法如下:
DB::select("SELECT * FROM users WHERE name = '" . $request->input('name') . "'")
这相当于直接绕过了框架提供的保护。
合规写法应该是:
DB::select("SELECT * FROM users WHERE name = ?", [$request->input('name')])
同样需要注意的是,DB::raw() 并不会让用户输入自动变安全。下面这种包一层单引号的方式也挡不住注入:
DB::raw("'$request->input('sort')'")
为什么说 ORM 安全不等于项目安全
很多 Laravel 项目的问题,不是出在常规的 where()、first()、paginate(),而是出在“局部绕过”。例如:
whereRaw()被包在when()里时,开发者忘了传绑定参数;- 动态排序字段在
orderByRaw()中直接拼接; - 验证规则如
ignore()一类场景里,字段名或结构值来源不明确; - 看似只是做搜索高亮、模糊匹配、统计字段切换,实际把用户输入带进了 SQL 结构。
所以安全边界并不只在框架层,而在每一处“用户输入”和“SQL 结构”相遇的地方。
一份可直接落地的排查清单
- 检查项目中所有
whereRaw()、selectRaw()、orderByRaw()是否都使用了绑定参数。 - 检查
DB::select()、DB::statement()是否存在字符串拼接。 - 检查动态列名、表名、排序字段是否使用白名单或映射数组。
- 检查请求验证层,能否通过
in:规则提前限制可选字段。 - 检查
DB::raw()中是否包裹了用户输入。 - 检查条件分支、查询封装和辅助函数里,是否存在“局部改回拼接 SQL”的情况。
一句话总结:值用参数绑定,结构用白名单控制。只要用户输入开始影响 SQL 语法本身,Laravel 默认的安全能力就帮不上忙了。








