Laravel 的请求生命周期中,中间件(Middleware)构成了第一道防线。在防范 SQL 注入和 XSS 攻击时,很多人过度依赖 ORM 的自动转义或前端框架的自动转义,却忽略了在中间件层建立纵深防御体系。实际上,SQL 注入和 XSS 的防御层级完全不同,一个主要发生在数据进入数据库之前,另一个主要发生在数据输出到浏览器之前。理解这两者在中间件中的处理边界,才能真正堵住漏洞。
SQL 注入的防御核心不在中间件先给一个明确的结论:SQL 注入的本质是将用户输入的数据当作 SQL 命令执行,而 Laravel 的 Eloquent ORM 和查询构建器默认使用 PDO 参数绑定,已经从根本上杜绝了 99% 的注入场景。只要你使用 where('column', $value) 而非 whereRaw("column = '$value'"),注入攻击就无从下手。中间件在这里的角色不是去拦截或过滤输入,而是做环境加固和异常请求阻断。比如,你可以在一个全局中间件中检测请求参数中是否包含典型的 SQL 注入探测字符串,如 UNION SELECT、1=1、-- 等模式,一旦命中直接返回 403 状态码并记录告警日志。这不是防御注入本身,而是降低攻击者的探测效率,同时为安全团队提供早期预警。
在 Laravel 中创建一个中间件,对所有的 GET 和 POST 输入进行模式匹配,可以有效拦截自动化扫描工具。以下是一个基础实现:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SqlInjectionGuard
{
protected $patterns = [
'/\bUNION\b.*\bSELECT\b/i',
'/\bSELECT\b.*\bFROM\b/i',
'/\bINSERT\b\s+INTO\b/i',
'/\bUPDATE\b\s+\w+\s+SET\b/i',
'/\bDELETE\b\s+FROM\b/i',
'/\bDROP\b\s+(TABLE|DATABASE)\b/i',
'/\bOR\b\s+\'1\'=\'1/i',
'/--\s*$/i',
'/;\s*$/i',
];
public function handle(Request $request, Closure $next)
{
$input = $request->all();
array_walk_recursive($input, function ($value) {
if (is_string($value)) {
foreach ($this->patterns as $pattern) {
if (preg_match($pattern, $value)) {
abort(403, 'Malicious request detected.');
}
}
}
});
return $next($request);
}
}
这个中间件注册为全局中间件后,每个请求都会经过检测。但必须注意,这种基于正则的检测存在误报风险。比如用户在富文本编辑器中正常讨论 SQL 语句时,输入内容可能包含 SELECT 关键字。因此,这个中间件更适合作为辅助防御层,而不是唯一的依赖。更精细的做法是结合路由分组,只对特定的 API 端点启用严格模式,对允许富文本输入的路由则放宽规则或跳过检测。
XSS 防御的中间件策略与输出编码XSS 攻击的防御逻辑和 SQL 注入正好相反。SQL 注入要在数据入库前处理,XSS 则要在数据输出时处理。很多开发者犯的错误是在入库前对用户输入进行 HTML 实体编码,这会导致数据库中存储的是编码后的数据,当数据需要在非 HTML 场景(如 API 返回 JSON、移动端展示、邮件发送)中使用时,反而出现乱码或二次编码问题。正确的做法是:数据库存储原始用户输入,在输出到不同上下文时进行对应的编码处理。中间件在这里可以承担输出编码的职责,但需要区分响应类型。
响应中间件的上下文编码创建一个响应中间件,根据响应的 Content-Type 决定是否对输出内容进行 HTML 编码。对于 text/html 类型的响应,中间件可以遍历响应数据,对用户提交的内容进行自动转义。但 Laravel 的 Blade 模板引擎已经默认使用了 {{ }} 双花括号语法进行 HTML 实体编码,只有使用 {!! !!} 才会输出原始 HTML。因此,中间件的职责更多是兜底:确保所有视图变量都经过编码,并检测是否有开发者误用了原始输出语法。以下是一个检测响应中是否包含未转义用户输入的中间件示例:
namespace App\Http\Middleware;
use Closure;
class XssOutputGuard
{
public function handle($request, Closure $next)
{
$response = $next($request);
if ($this->isHtmlResponse($response)) {
$content = $response->getContent();
// 检测常见的XSS payload特征
$dangerous = [
'/