跨站点请求伪造保护

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

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

工作原理

CSRF 保护基于以下内容

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

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

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

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

    此部分由模板标记完成。

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

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

    此检查由 CsrfViewMiddleware 完成。

  4. CsrfViewMiddleware 验证 Origin 头(如果由浏览器提供),并将其与当前主机和 CSRF_TRUSTED_ORIGINS 设置进行比较。这提供了针对跨子域攻击的保护。

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

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

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

    可以使用 CSRF_TRUSTED_ORIGINS 设置将接受的引用扩展到当前主机或 cookie 域之外。

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

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

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

移除 Referer

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

限制

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

实用工具

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

csrf_exempt(view)

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

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 中已更改

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

常见问题

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

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

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

Django 的 CSRF 保护默认情况下未链接到会话,这是否是个问题?

否,这是设计使然。不将 CSRF 保护链接到会话,可以在允许匿名用户(没有会话)提交内容的网站(例如 pastebin)上使用此保护。

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

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

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

返回顶部