跨站请求伪造保护

The CSRF middleware and template tag provides easy-to-use protection against Cross Site Request Forgeries. This type of attack occurs when a malicious website contains a link, a form button or some JavaScript that is intended to perform some action on your website, using the credentials of a logged-in user who visits the malicious site in their browser. A related type of attack, 'login CSRF', where an attacking site tricks a user's browser into logging into a site with someone else's credentials, is also covered.

The first defense against CSRF attacks is to ensure that GET requests (and other 'safe' methods, as defined by RFC 9110#section-9.2.1) are side effect free. Requests via 'unsafe' methods, such as POST, PUT, and DELETE, can then be protected by the steps outlined in 如何使用 Django 提供的 CSRF 防护功能.

工作方式

CSRF 保护是基于以下几点:

  1. A CSRF cookie that is a random secret value, which other sites will not have access to.

    CsrfViewMiddleware sends this cookie with the response whenever django.middleware.csrf.get_token() is called. It can also send it in other cases. For security reasons, the value of the secret is changed each time a user logs in.

  2. A hidden form field with the name 'csrfmiddlewaretoken', present in all outgoing POST forms.

    In order to protect against BREACH attacks, the value of this field is not simply the secret. It is scrambled differently with each response using a mask. The mask is generated randomly on every call to get_token(), so the form field value is different each time.

    这一部分是由模板标签来完成的。

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

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

    这个检查是由 CsrfViewMiddleware 完成的。

  4. CsrfViewMiddleware 根据当前主机和 CSRF_TRUSTED_ORIGINS 的设置,验证 Origin header ,如果是由浏览器提供的。这提供了对跨子域攻击的保护。

  5. 此外,对于 HTTPS 请求,如果没有提供 Origin 头,CsrfViewMiddleware 会执行严格的来源检查。这意味着,即使一个子域可以设置或修改你的域名上的 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 设置,可以将接受的 referer 扩展到当前主机或 cookie 域之外。

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

It deliberately ignores GET requests (and other requests that are defined as 'safe' by RFC 9110#section-9.2.1). These requests ought never to have any potentially dangerous side effects, and so a CSRF attack with a GET request ought to be harmless. RFC 9110#section-9.2.1 defines POST, PUT, and DELETE as 'unsafe', and all other methods are also assumed to be unsafe, for maximum protection.

CSRF 保护不能防止中间人攻击,所以使用 HTTPSHTTP 严格传输安全。它还假设 验证 HOST 头 和你的网站上没有任何 跨站脚本漏洞 (因为 XSS 漏洞已经让攻击者做了 CSRF 漏洞允许的任何事情,甚至更糟)。

删除 Referer

为了避免向第三方网站透露 referrer URL,你可能想在你的网站的 <a> 标签上 禁用 referrer 。例如,你可以使用 <meta name="referrer" content="no-referrer"> 标签或包含 Referrer-Policy: no-referrer 头。由于 CSRF 保护对 HTTPS 请求进行严格的 referer 检查,这些技术会在使用“不安全”方法的请求上导致 CSRF 失败。取而代之的是,使用诸如 <a rel="noreferrer" ...>" 这样的替代品来链接第三方网站。

限制

Subdomains within a site will be able to set cookies on the client for the whole domain. By setting the cookie and using a corresponding token, subdomains will be able to circumvent the CSRF protection. The only way to avoid this is to ensure that subdomains are controlled by trusted users (or, are at least unable to set cookies). Note that even without CSRF, there are other vulnerabilities, such as session fixation, that make giving subdomains to untrusted parties a bad idea, and these vulnerabilities cannot easily be fixed with current browsers.

实用程序

下面的例子假设你使用的是基于函数的视图。如果你正在使用基于类的视图,你可以参考 装饰基于类的视图

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")
Changed in Django 5.0:

Support for wrapping asynchronous view functions was added.

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)
Changed in Django 5.0:

Support for wrapping asynchronous view functions was added.

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)
Changed in Django 5.0:

Support for wrapping asynchronous view functions was added.

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

Changed in Django 5.0:

Support for wrapping asynchronous view functions was added.

常问问题

可以提交任意的 CSRF 令牌对(cookie 和 POST 数据)是漏洞吗?

不,这是设计好的。如果没有中间人攻击,攻击者就没有办法向受害者的浏览器发送 CSRF 令牌 cookie,所以成功的攻击需要通过 XSS 或类似的方式获得受害者浏览器的 cookie,在这种情况下,攻击者通常不需要 CSRF 攻击。

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

Django 的 CSRF 保护默认不与会话关联,是不是有问题?

不,这是设计好的。不将 CSRF 保护与会话联系起来,就可以在诸如 pastebin 这样允许匿名用户提交的网站上使用保护,而这些用户并没有会话。

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

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

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