Laravel 的 Blade 模板引擎默认对 "{{ }}" 输出进行转义,这本身就是一套内置的 XSS 过滤机制。但很多人误以为只要用了 Blade 就绝对安全,实际上当你使用 "{!! !!}"、原生 PHP 回显、数据库存 HTML、富文本编辑器或 API 返回 JSON 嵌入前端时,默认转义会被绕过或失效。要真正开启并强化这套防 XSS 机制,需要从输出转义、输入清洗、内容安全策略、Cookie 属性以及特殊场景处理五个层面同时入手。

先确认默认转义没有被破坏

Blade 的 "{{ $var }}" 会调用 "htmlspecialchars",默认使用 UTF-8,并转换单引号和双引号。这意味着大多数输出场景已经安全。但检查你的代码里是否存在 "{!! $var !!}" 或 "@php echo $var; @endphp",这两类写法会完全跳过转义。如果必须输出 HTML,应改用 "e()" 辅助函数手动转义后再拼接,或者使用 "HtmlString" 对象明确标记为可信内容。

// 安全写法
{{ $userInput }}

// 危险写法,跳过转义
{!! $userInput !!}

// 手动转义
{{ e($userInput) }}

另一个容易被忽略的地方是 "@json" 指令。Blade 的 "@json($data)" 默认不转义特殊字符,如果数据中包含用户输入,直接嵌入 "<script>" 标签会形成 XSS 漏洞。应使用 "JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT" 选项进行安全输出。

// 不安全


// 安全
输入阶段就要做第一层过滤

Laravel 的请求对象本身不会自动过滤 XSS 内容,它只负责取到原始输入。很多人以为 "$request->input('name')" 返回的数据已经被清理,这是错误认知。你需要在数据进入业务逻辑或数据库之前,对字符串类型输入做一次白名单或黑名单过滤。推荐使用 "strip_tags" 去除所有 HTML 和 PHP 标签,再根据业务需要决定是否保留某些安全标签。

use Illuminate\Support\Str;

$cleanInput = strip_tags($request->input('content'));
$cleanInput = Str::of($cleanInput)->trim();

如果业务需要保留部分格式,比如 ""、"<i>"、"<a>",不要自己写正则去过滤,正则处理 HTML 极其容易出错。应该使用专门的 HTML 净化库,例如 "mews/purifier",它基于 HTMLPurifier,能按白名单保留标签和属性,同时移除事件属性、"javascript:" 协议等危险内容。

composer require mews/purifier

// 配置后使用
$cleanHtml = clean($request->input('rich_content'));

对于富文本编辑器提交的内容,这是 XSS 的重灾区。编辑器生成的 HTML 往往包含大量样式和标签,直接存入数据库再原样输出非常危险。必须经过 HTMLPurifier 这类服务端白名单过滤,并且过滤要在服务端执行,不能只依赖前端编辑器的过滤,因为前端过滤可以被绕过。

强化 Cookie 与会话安全属性

XSS 攻击的常见目标是窃取会话 Cookie。即使你的输出过滤做得再好,一旦存在一个漏网之鱼的反射型或存储型 XSS,攻击者就能通过 JavaScript 读取 "document.cookie"。Laravel 允许在 "config/session.php" 中设置 Cookie 的 "http_only" 和 "same_site" 属性。"http_only" 设为 "true" 后,JavaScript 无法读取该 Cookie,能有效降低 XSS 窃取会话的风险。"same_site" 设为 "lax" 或 "strict" 可以防止跨站请求携带 Cookie,间接减少 CSRF 与某些 XSS 结合攻击的可能性。

// config/session.php
'http_only' => true,
'same_site' => 'lax',
'secure' => env('SESSION_SECURE_COOKIE', true), // 生产环境必须为 true

同时检查 "config/session.php" 中的 "encrypt" 选项。开启后 Cookie 数据会被加密,即使被窃取也无法直接读取明文内容。虽然这不能阻止 XSS 执行,但增加了攻击者利用窃取数据的难度。

设置内容安全策略响应头

内容安全策略(CSP)是防御 XSS 的第二道防线。它告诉浏览器只允许从指定来源加载脚本、样式、图片等资源,并且可以禁止内联脚本执行。Laravel 没有内置 CSP 中间件,但你可以自己创建一个中间件,在响应头中加入 CSP 策略。对于 Laravel 应用,比较实用的策略是 "default-src 'self'",然后根据实际需要放行 CDN、字体、图片等资源域名。关键点是避免使用 "unsafe-inline" 和 "unsafe-eval",这两个值会大幅削弱 CSP 的防护能力。

php artisan make:middleware ContentSecurityPolicy

// 中间件 handle 方法中
$response = $next($request);
$response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
return $response;

如果你的应用使用了 Vue、React 等前端框架,并且通过 Laravel Mix 或 Vite 打包,通常脚本文件与页面同源,"script-src 'self'" 可以正常工作。但如果你使用了内联事件处理器如 "onclick="..."",CSP 会直接阻止执行。这其实是好事,迫使你把事件绑定移到外部 JS 文件中,从根源上减少 XSS 注入点。

处理 URL 与跳转参数中的 XSS 向量

XSS 不只存在于 HTML 标签中,URL 参数、跳转地址、"href" 属性、"src" 属性同样可以注入 "javascript:" 协议。Laravel 的 "redirect()->away()" 和 "redirect()->to()" 不会自动校验协议,如果用户可控参数被拼进跳转地址,就可能形成 "javascript:alert(1)" 这类 XSS。处理这类场景时,必须校验目标 URL 的协议只允许 "http" 和 "https",并拒绝包含换行符或控制字符的地址。

use Illuminate\Support\Str;

$targetUrl = $request->input('redirect_to');
if (!Str::startsWith($targetUrl, ['http://', 'https://'])) {
    $targetUrl = '/';
}
return redirect()->away($targetUrl);

对于输出到 "href" 或 "src" 属性的变量,除了用 "e()" 转义,还要额外检查是否以危险协议开头。Blade 的 "{{ }}" 只会转义 HTML 特殊字符,不会过滤 "javascript:" 协议,所以协议校验必须在业务逻辑中完成。

存储型 XSS 的数据库输出过滤

存储型 XSS 比反射型更危险,因为它会持久化在数据库中,影响所有访问者。很多项目在存入数据库前不做过滤,只在输出时转义,这在纯文本场景下是可行的。但一旦某个接口以 JSON 返回数据,前端用 "innerHTML" 或 "v-html" 渲染,输出转义就失效了。因此对于可能被前端以 HTML 方式渲染的字段,最好在写入数据库前就完成白名单过滤。这样即使前端某个地方忘了转义,数据库里存的也是安全内容。

但要注意,对数据库内容做 HTML 净化不等于可以完全信任输出。输出转义仍然是必须的,因为净化规则可能不完善,或者攻击者通过其他途径直接修改了数据库。两层防护叠加才能把风险降到最低。

API 与 JSON 响应中的 XSS 风险

Laravel 的 JSON 响应默认使用 "application/json" Content-Type,浏览器不会将其中的内容当作 HTML 执行。但如果前端拿到 JSON 后通过 "innerHTML"、"document.write" 或 jQuery 的 ".html()" 插入页面,XSS 就会发生。这种情况下后端无法完全阻止,只能通过前端框架的自动转义机制来防护。Vue 和 React 默认会对插值表达式进行转义,但 "v-html" 和 "dangerouslySetInnerHTML" 会绕过转义。后端能做的就是在 JSON 返回前,对富文本字段使用净化库处理,并确保 API 文档中明确标注哪些字段可能包含 HTML,提醒前端开发者使用安全渲染方式。

另外,如果 JSON 被嵌入到 "<script>" 标签中作为 JavaScript 变量使用,必须注意 "</script>" 字符串会提前闭合标签。Laravel 的 "@json" 指令配合 "JSON_HEX_TAG" 可以避免这个问题,将 "<" 和 ">" 转义为 "\u003C" 和 "\u003E",这样既不影响 JSON 解析,又防止了标签注入。

文件上传与 SVG 中的 XSS 向量

很多人忽略 SVG 文件可以包含 "<script>" 标签。如果你的 Laravel 应用允许用户上传图片,并且没有限制文件类型,攻击者可以上传一个带有恶意脚本的 SVG 文件,然后通过 "<img src="/uploads/evil.svg">" 或直接访问该文件触发 XSS。处理文件上传时,除了验证 MIME 类型和扩展名,对于 SVG 这类 XML 格式文件,应该使用净化器移除脚本标签,或者干脆禁止上传 SVG,强制转换为 PNG、JPEG 等位图格式。

对于用户上传的其他文件,应通过 "Storage::disk('local')" 存储在非公开目录,再通过受控路由输出,并设置正确的 Content-Disposition 响应头,避免浏览器直接渲染 HTML 或 SVG 内容。

return response()->file($path, [
    'Content-Type' => 'application/octet-stream',
    'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
利用 Laravel 的中间件统一过滤输入

与其在每个控制器方法里重复写过滤逻辑,不如创建一个全局中间件,对所有 "$request->input()" 返回的字符串数据做一次基础清理。但要注意,全局过滤不能使用 "strip_tags" 一刀切,因为有些接口确实需要接收 HTML。比较合理的做法是只对明显危险的模式做处理,比如移除 "javascript:" 协议、"onerror"、"onclick" 等事件属性,以及 "<script>"、"<iframe>"、"<object>" 等高风险标签。这种轻量级过滤不会破坏正常业务数据,但能拦截大部分自动化扫描和简单攻击。

// 中间件中对输入数组递归处理
array_walk_recursive($input, function (&$value) {
    if (is_string($value)) {
        $value = preg_replace('/]*>(.*?)<\/script>/is', '', $value);
        $value = preg_replace('/\son\w+\s*=\s*("[^"]*"|\'[^\']*\')/i', '', $value);
        $value = preg_replace('/\s*href\s*=\s*["\']\s*javascript:[^"\']*["\']/i', '', $value);
    }
});

这种基于正则的过滤只能作为辅助手段,不能替代输出转义和 HTMLPurifier 白名单净化。它的价值在于拦截明显的恶意载荷,减少到达业务层和数据库的危险内容。

定期审计与自动化测试

开启 Laravel 的防 XSS 机制不是一次性配置,而是一个持续过程。你应该在代码审查中重点关注 "{!! !!}"、"@php"、"v-html"、"innerHTML"、"document.write" 等危险用法,并建立自动化测试用例,模拟包含 "<script>alert(1)</script>"、""><img src=x onerror=alert(1)>" 等常见 payload 的输入,验证输出是否被正确转义或过滤。Laravel 的测试框架可以很方便地断言响应内容中不包含未转义的恶意字符串。

public function test_xss_payload_is_escaped()
{
    $response = $this->post('/comment', ['content' => '']);
    $response->assertSee('<script>alert(1)</script>');
    $response->assertDontSee('', false);
}

同时建议在 "AppServiceProvider" 中注册一个 "Blade::directive" 或自定义组件,统一处理需要输出 HTML 的字段,避免开发者直接使用 "{!! !!}"。通过封装,你可以在输出前自动调用净化器,并记录日志,方便追踪哪些地方在输出未转义内容。

Laravel 的默认转义已经挡住了大部分普通 XSS 攻击,但真正的安全需要你把默认机制用对、用全,并在输入、输出、存储、响应头、文件上传等各个环节都建立过滤和校验。只有把内置的转义功能和额外的净化、CSP、Cookie 属性结合起来,才能称得上真正开启了 Laravel 的防 XSS 内置过滤机制。