跨站请求伪造 (CSRF) 保护

CSRF 中间件和模板标签提供了易于使用的针对跨站请求伪造 (CSRF)的保护。这种类型的攻击发生在恶意网站包含链接、表单按钮或一些 JavaScript 代码,这些代码旨在使用在浏览器中访问恶意网站的已登录用户的凭据,在您的网站上执行某些操作时。相关的攻击类型,“登录 CSRF”,攻击网站诱骗用户的浏览器使用其他人的凭据登录某个站点,也包括在内。

抵御 CSRF 攻击的第一道防线是确保 GET 请求(以及其他“安全”方法,如RFC 9110 第 9.2.1 节中所定义)没有副作用。然后可以通过如何使用 Django 的 CSRF 保护中概述的步骤来保护使用“不安全”方法(如 POST、PUT 和 DELETE)的请求。

工作原理

CSRF 保护基于以下内容:

  1. 一个 CSRF cookie,它是一个随机的秘密值,其他网站无法访问。

    CsrfViewMiddleware在调用django.middleware.csrf.get_token()时,会将此 cookie 与响应一起发送。它也可以在其他情况下发送它。出于安全原因,每次用户登录时,秘密的值都会更改。

  2. 一个名为“csrfmiddlewaretoken”的隐藏表单字段,存在于所有传出的 POST 表单中。

    为了防止BREACH攻击,此字段的值不仅仅是秘密。它使用掩码在每次响应中都会被不同的方式加密。掩码在每次调用get_token()时都会随机生成,因此表单字段的值每次都不同。

    这部分由csrf_token模板标签完成。

  3. 对于所有不使用 HTTP GET、HEAD、OPTIONS 或 TRACE 的传入请求,必须存在 CSRF cookie,并且“csrfmiddlewaretoken”字段必须存在且正确。如果不存在,用户将收到 403 错误。

    验证“csrfmiddlewaretoken”字段值时,只将秘密(而不是完整的令牌)与 cookie 值中的秘密进行比较。这允许使用不断变化的令牌。虽然每个请求都可能使用自己的令牌,但秘密对所有令牌都是通用的。

    此检查由CsrfViewMiddleware完成。

  4. CsrfViewMiddleware验证浏览器提供的Origin 标头(如果提供),是否与当前主机和CSRF_TRUSTED_ORIGINS设置相匹配。这提供了针对跨子域攻击的保护。

  5. 此外,对于 HTTPS 请求,如果未提供Origin标头,CsrfViewMiddleware将执行严格的 Referer 检查。这意味着,即使子域可以在您的域上设置或修改 cookie,它也无法强制用户向您的应用程序发布数据,因为该请求不会来自您自己的确切域。

    这还可以解决使用会话独立密钥时 HTTPS 下可能发生的中间人攻击,因为 HTTP Set-Cookie标头(不幸的是)即使在与 HTTPS 下的站点通信时也会被客户端接受。(对于 HTTP 请求不执行 Referer 检查,因为在 HTTP 下Referer标头的存在不够可靠。)

    如果设置了CSRF_COOKIE_DOMAIN设置,则 Referer 将与其进行比较。您可以通过包含一个前导点来允许跨子域请求。例如,CSRF_COOKIE_DOMAIN = '.example.com'将允许来自www.example.comapi.example.com的 POST 请求。如果没有设置此设置,则 Referer 必须与 HTTP Host标头匹配。

    可以使用CSRF_TRUSTED_ORIGINS设置来扩展超出当前主机或 cookie 域的已接受 Referer。

这确保只有来自可信域的表单才能用于将数据 POST 回来。

它故意忽略 GET 请求(以及RFC 9110 第 9.2.1 节中定义为“安全”的其他请求)。这些请求永远不应该有任何潜在的危险副作用,因此使用 GET 请求的 CSRF 攻击应该无害。RFC 9110 第 9.2.1 节将 POST、PUT 和 DELETE 定义为“不安全”,为了最大限度地保护,所有其他方法也被假定为不安全。

CSRF 保护无法防止中间人攻击,因此请使用HTTPSHTTP 严格传输安全 (HSTS)。它还假定HOST 标头的验证以及您的站点上没有任何跨站脚本 (XSS) 漏洞(因为 XSS 漏洞已经允许攻击者执行 CSRF 漏洞允许的任何操作以及更糟糕的操作)。

移除 Referer 标头

为了避免向第三方网站泄露推荐 URL,您可能希望禁用网站的 <a> 标签上的推荐信息。例如,您可以使用<meta name="referrer" content="no-referrer">标签或包含Referrer-Policy: no-referrer标头。由于 CSRF 保护对 HTTPS 请求进行严格的 Referer 检查,这些技术会导致对使用“不安全”方法的请求出现 CSRF 失败。相反,对于指向第三方网站的链接,请使用<a rel="noreferrer" ...>"之类的替代方法。

限制

站点内的子域将能够为整个域设置客户端的 cookie。通过设置 cookie 并使用相应的令牌,子域将能够规避 CSRF 保护。避免这种情况的唯一方法是确保子域由可信用户控制(或者至少无法设置 cookie)。请注意,即使没有 CSRF,也存在其他漏洞,例如会话固定,这使得将子域提供给不可信的各方成为一个坏主意,而这些漏洞无法通过当前的浏览器轻松修复。

实用程序

以下示例假定您使用的是基于函数的视图。如果您使用的是基于类的视图,您可以参考装饰基于类的视图

csrf_exempt(view)[source]

此装饰器将视图标记为免受中间件确保的保护。示例

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt


@csrf_exempt
def my_view(request):
    return HttpResponse("Hello world")
Django 5.0 中已更改

添加了包装异步视图函数的支持。

csrf_protect(view)

为视图提供CsrfViewMiddleware保护的装饰器。

用法

from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect


@csrf_protect
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)
Django 5.0 中已更改

添加了包装异步视图函数的支持。

requires_csrf_token(view)

通常,如果CsrfViewMiddleware.process_view或类似csrf_protect之类的等效项未运行,则csrf_token模板标签将不起作用。requires_csrf_token视图装饰器可用于确保模板标签有效。此装饰器的作用类似于csrf_protect,但从不拒绝传入请求。

示例

from django.shortcuts import render
from django.views.decorators.csrf import requires_csrf_token


@requires_csrf_token
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)
Django 5.0 中已更改

添加了包装异步视图函数的支持。

此装饰器强制视图发送 CSRF cookie。

Django 5.0 中已更改

添加了包装异步视图函数的支持。

设置

许多设置可用于控制 Django 的 CSRF 行为

常见问题

发布任意 CSRF 令牌对(cookie 和 POST 数据)是否是一个漏洞?

不,这是设计使然。如果没有中间人攻击,攻击者无法将 CSRF 令牌 Cookie 发送到受害者的浏览器,因此成功的攻击需要通过 XSS 或类似方式获取受害者浏览器的 Cookie,在这种情况下,攻击者通常不需要 CSRF 攻击。

一些安全审计工具将此标记为问题,但如前所述,攻击者无法窃取用户的浏览器 CSRF Cookie。“窃取”或使用 Firebug、Chrome 开发者工具等修改_您自己_的令牌并非漏洞。

Django 的 CSRF 保护默认情况下不与会话关联,这是一个问题吗?

不,这是设计使然。不将 CSRF 保护与会话关联允许在诸如允许匿名用户(没有会话)提交内容的粘贴板之类的站点上使用此保护。

如果希望将 CSRF 令牌存储在用户的会话中,请使用 CSRF_USE_SESSIONS 设置。

为什么用户登录后可能会遇到 CSRF 验证失败?

出于安全原因,每次用户登录时都会轮换 CSRF 令牌。登录前生成的包含表单的任何页面都将具有旧的、无效的 CSRF 令牌,需要重新加载。如果用户登录后使用后退按钮,或者在不同的浏览器标签页中登录,则可能会发生这种情况。

返回顶部