CSRF 保护

未匹配的标注
本文档最新版为 12.x,旧版本可能放弃维护,推荐阅读最新版!

CSRF 保护(CSRF Protection)

简介

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种恶意攻击方式,攻击者会在用户已认证(已登录)的情况下,冒充用户执行未授权的操作。

幸运的是,Laravel 可以非常轻松地保护你的应用免受 Cross-Site Request Forgery (CSRF)攻击。

漏洞原理说明

如果你对跨站请求伪造(CSRF)还不熟悉,我们来看一个漏洞是如何被利用的示例。

假设你的应用程序有一个 /user/email 路由,它接收 POST 请求,用于修改当前登录用户的邮箱地址。

这个路由大概率会接收一个 email 输入字段,用来保存用户希望更改的新邮箱地址。

如果没有 CSRF 保护,一个恶意网站就可以创建一个 HTML 表单,偷偷请求你应用中的 /user/email 路由,并把邮箱改成攻击者自己的邮箱:

<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 请求,以验证其中是否包含恶意应用程序无法访问的秘密 Session 值。

防止 CSRF 请求

默认包含在 web 中间件组中的 Illuminate\Foundation\Http\Middleware\PreventRequestForgery 中间件,通过双层方式保护你的应用程序免受跨站请求伪造(CSRF)攻击。

首先,该中间件会检查浏览器的 Sec-Fetch-Site 请求头。现代浏览器会在每个请求中自动设置此请求头,用于指示请求是否来自相同源(same origin)、相同站点(same site)或跨站来源(cross-site source)。如果请求头表明请求来自相同源,则会立即允许该请求,而无需进行任何 token 验证。

如果来源验证未通过 —— 例如,请求来自不发送 Sec-Fetch-Site 请求头的旧版浏览器,或者连接不安全 —— 中间件将回退到传统的 CSRF token 验证方式。

Laravel 会为应用程序管理的每个活动 用户会话 自动生成一个 CSRF “token”。此 token 用于验证已认证用户是否确实是向应用程序发起请求的人。由于该 token 存储在用户 session 中,并且每次 session 重新生成时都会发生变化,因此恶意应用程序无法访问它。

当前 session 的 CSRF token 可以通过请求的 session 或 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 指令来生成隐藏的 token 输入字段:

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

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

CSRF Token 与 SPA

如果你正在构建一个使用 Laravel 作为 API 后端的 SPA(单页应用程序),你应该查阅 Laravel Sanctum 文档,以获取有关 API 身份验证以及防止 CSRF 漏洞的信息。

来源验证(Origin Verification)

如上所述,Laravel 的请求伪造中间件首先会检查 Sec-Fetch-Site 请求头,以确定请求是否来自相同源。

默认情况下,如果该检查未通过,中间件将回退到 CSRF token 验证。

不过,如果你希望完全依赖来源验证,并完全禁用 CSRF token 的回退验证机制,你可以在应用程序的 bootstrap/app.php 文件中使用 preventRequestForgery 方法:

->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(originOnly: true);
})

当使用仅来源验证模式(origin-only mode)时,来源验证失败的请求将收到 403 HTTP 响应,而不是通常与 CSRF token 不匹配相关联的 419 响应。

[!警告]
Sec-Fetch-Site 请求头仅会由浏览器在安全(HTTPS)连接中发送。如果你的应用程序未通过 HTTPS 提供服务,则来源验证将不可用,中间件将回退到 CSRF token 验证。

如果你的应用程序需要接受来自子域名的请求(例如,dashboard.example.com 接受来自 example.com 的请求),你可以允许同站点(same-site)请求,而不仅仅是相同源(same-origin)请求:

->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(allowSameSite: true);
})

从 CSRF 保护中排除 URI

有时候,你可能希望将一组 URI 排除在 CSRF 保护之外。例如,如果你正在使用 Stripe 处理支付,并使用它们的 webhook 系统,那么你需要将 Stripe webhook 处理路由从 CSRF 保护中排除,因为 Stripe 不会知道应该向你的路由发送什么 CSRF token。

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

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

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

X-CSRF-TOKEN

除了将 CSRF token 作为 POST 参数进行检查之外,PreventRequestForgery 中间件还会检查 X-CSRF-TOKEN 请求头。例如,你可以将 token 存储在 HTML 的 meta 标签中:

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

然后,你可以指示像 jQuery 这样的库自动将 token 添加到所有请求头中。这为基于 AJAX 的应用程序使用传统 JavaScript 技术提供了简单且方便的 CSRF 防护:

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

X-XSRF-TOKEN

Laravel 会将当前的 CSRF token 存储在一个加密的 XSRF-TOKEN Cookie 中,并随框架生成的每个响应一起返回。你可以使用该 Cookie 的值来设置 X-XSRF-TOKEN 请求头。

该 Cookie 主要是为了方便开发者,因为某些 JavaScript 框架和库(例如 Angular 和 Axios)会在同源请求中自动将其值放入 X-XSRF-TOKEN 请求头中。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/13.x/cs...

译文地址:https://learnku.com/docs/laravel/13.x/cs...

上一篇 下一篇
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
贡献者:1
讨论数量: 1
发起讨论 只看当前版本


FakeSPrite
input 里面的 csrf 和 @csrf 的区别?
0 个点赞 | 5 个回复 | 问答 | 课程版本 5.8