CSRF保护

介绍

跨站请求伪造(CSRF)是一种恶意利用方式,通过在已认证用户的身份下执行未经授权的命令。幸运的是,Laravel 使得保护应用免受【跨站请求伪造】(CSRF)攻击变得十分容易。

漏洞解释

如果你不熟悉跨站请求伪造漏洞,下面是一个例子,展示了这种漏洞是如何被利用的。假设你的应用有一个 /user/email 路由,它接受一个 POST 请求,用于更改已认证用户的电子邮件地址。很可能,这个路由会期望一个 email 输入字段,来接收用户想要使用的新电子邮件地址。

如果没有 CSRF 保护,恶意网站可能会创建一个指向你应用 /user/email 路由的 HTML 表单,并提交恶意用户自己的电子邮件地址:

<form action="https://your-application.com/user/email" method="POST">
    <input type="email" value="malicious-email@example.com">
</form>

<script>
    document.forms[0].submit();
</script>

如果恶意网站在页面加载时自动提交表单,恶意用户只需要诱使一个无辜的用户访问他们的网站,这样该用户的电子邮件地址就会在你的应用中被更改。

为了防止这个漏洞,我们需要检查每个传入的 POSTPUTPATCHDELETE 请求,确保它们包含一个恶意应用无法访问的秘密会话值。

防止 CSRF 请求

Laravel 会为每个由应用管理的活动【用户会话】自动生成一个 CSRF "令牌"。这个令牌用于验证发起请求的用户是否为经过身份验证的用户。由于这个令牌存储在用户的会话中,并且每次会话重新生成时都会改变,恶意应用无法访问它。

当前会话的 CSRF 令牌可以通过请求的会话或者通过 csrf_token 辅助函数来访问:

use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    $token = $request->session()->token();

    $token = csrf_token();

    // ...
});

每当你在应用中定义一个 "POST"、"PUT"、"PATCH" 或 "DELETE" 的 HTML 表单时,你应该在表单中包含一个隐藏的 CSRF _token 字段,以便 CSRF 保护中间件验证请求。为了方便,你可以使用 @csrf Blade 指令来生成隐藏的令牌输入字段:

<form method="POST" action="/profile">
    @csrf

    <!-- 等同于... -->
    <input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>

默认情况下,Illuminate\Foundation\Http\Middleware\ValidateCsrfToken 【中间件】包含在 web 中间件组中,它会自动验证请求输入中的令牌是否与存储在会话中的令牌匹配。当这两个令牌匹配时,我们就可以确定是经过身份验证的用户发起了这个请求。

CSRF 令牌与 SPA

如果你正在构建一个使用 Laravel 作为 API 后端的单页面应用(SPA),你应该参考 【Laravel Sanctum 文档】,了解如何使用 API 进行身份验证并防止 CSRF 漏洞。

从 CSRF 保护中排除 URI

有时候,你可能希望将一组 URI 排除在 CSRF 保护之外。例如,如果你使用 【Stripe】 处理支付,并且使用了它们的 Webhook 系统,你将需要将 Stripe Webhook 处理路由排除在 CSRF 保护之外,因为 Stripe 不会知道该向你的路由发送什么 CSRF 令牌。

通常,应该将这些类型的路由放在 Laravel 应用的 routes/web.php 文件中,应用于所有路由的 web 中间件组之外。然而,你也可以通过在应用程序的 bootstrap/app.php 文件中的 validateCsrfTokens 方法中提供它们的 URI 来排除特定的路由:

->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'stripe/*',
        'http://example.com/foo/bar',
        'http://example.com/foo/*',
    ]);
})

为了方便,在 运行测试 时,CSRF 中间件会自动禁用所有路由。

X-CSRF-Token

除了检查作为 POST 参数传递的 CSRF 令牌之外,Illuminate\Foundation\Http\Middleware\ValidateCsrfToken 中间件(默认包含在 web 中间件组中)还会检查 X-CSRF-TOKEN 请求头。例如,你可以将令牌存储在 HTML 的 meta 标签中:

<meta name="csrf-token" content="{{ csrf_token() }}">

然后,你可以指示像 jQuery 这样的库自动将令牌添加到所有请求头中。这为你的基于 AJAX 的应用程序提供了简单便捷的 CSRF 保护,特别适用于使用传统 JavaScript 技术的情况:

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

X-XSRF-Token

Laravel 将当前的 CSRF 令牌存储在一个加密的 XSRF-TOKEN cookie 中,该 cookie 会随着框架生成的每个响应一起发送。你可以使用这个 cookie 的值来设置 X-XSRF-TOKEN 请求头。

这个 cookie 主要作为开发者的便利,因为一些 JavaScript 框架和库(例如 Angular 和 Axios)会自动将其值放入 X-XSRF-TOKEN 头中,尤其是在同源请求中。

默认情况下,resources/js/bootstrap.js 文件中包含了 Axios HTTP 库,它会自动为你发送 X-XSRF-TOKEN 头。