必须手动绑定参数,db::select()等方法不自动绑定,须用?占位符或命名绑定传参,严禁字符串拼接,否则导致sql注入;whereraw()和selectraw()中用户输入也需同样防护,表名列名等标识符只能白名单校验。

DB::select() 等原生查询必须手动绑定参数
只要用了 DB::select()、DB::update()、DB::delete() 或 DB::insert() 这类原生方法,Laravel 就不会自动帮你做参数绑定——你传进去的 SQL 字符串会被原样发给数据库,变量拼接等于裸奔。
常见错误是直接字符串拼接:
DB::select("SELECT * FROM users WHERE id = " . $request->id);
攻击者传 1 OR 1=1 或 1; DROP TABLE users-- 就能触发注入。正确做法只有一条:始终用第二个参数传值数组,并用 ? 占位。
DB::select("SELECT * FROM users WHERE status = ? AND created_at > ?", [$status, $date])- 命名绑定也行:
DB::select("SELECT * FROM users WHERE id = :id", ['id' => $id]) - 别图省事写
"WHERE id = {$id}",哪怕你做了(int)$id—— 类型强转不能替代绑定,尤其当该变量后续还用于日志、缓存 key 或其他拼接场景时
whereRaw() 和 selectRaw() 中的用户输入必须隔离处理
whereRaw() 和 selectRaw() 是 Query Builder 里唯一“交出控制权”的地方。它们不走预处理流程,里面写的任何东西都会被当 SQL 解析。所以一旦混入用户输入,风险和手写原生查询一样高。
错误写法:
User::whereRaw("email LIKE '%{$q}%'")->get();
攻击者传 admin%\' -- 就可能绕过条件。安全写法只有两种:
- 用问号占位:
whereRaw("email LIKE ?", ['%' . $q . '%']) - 用命名绑定:
whereRaw("email LIKE :pattern", ['pattern' => '%' . $q . '%']) - 更推荐直接放弃
whereRaw():改用where('email', 'like', "%{$q}%"),语义清晰,且底层自动绑定
表名、列名、排序字段不能靠绑定,只能靠白名单校验
PDO 的 ? 占位符只能绑定值(values),不能绑定标识符(identifiers)——也就是表名、列名、ORDER BY 字段、GROUP BY 字段这些。所以像 DB::table($table)、orderBy($field)、select("product_variant_{$n}") 这类操作,变量绝不能来自用户输入,除非先过白名单。
典型危险代码:
$column = "product_variant_" . $request->input('variant_id');DB::table('products')->select($column)->get();
即使没用 selectRaw(),这也是字符串拼接,攻击者传 1 FROM users -- 可能让 SQL 变成 SELECT product_variant_1 FROM users -- FROM products。
- 控制器层首选
$request->validate(['variant_id' => 'required|in:1,2,3']) - 非 HTTP 场景(如队列任务)用
in_array($variant_id, [1, 2, 3], true) - 不要只依赖
(int)$variant_id:它能防语法错误,但无法阻止该值被用于其他上下文(比如缓存键拼接、日志记录)时带入恶意内容
动态列名最容易被忽略的隐患点
很多人以为“只要没用 selectRaw() 就安全”,这是最危险的错觉。只要列名是拼出来的,哪怕只是 "col_{$suffix}",就属于标识符注入范畴。而这类漏洞往往不出错——数据库可能静默忽略非法列名,或报错但不回显,导致问题长期潜伏。
真正关键的是:你得清楚哪些地方 Laravel 自动防护(where()、update())、哪些地方它完全不管(所有标识符 + Raw 方法)。安全不是靠框架兜底,而是靠你在每个拼接点主动设防。











