跨站请求伪造 (CSRF) 保护¶
CSRF 中间件和模板标签提供了易于使用的针对跨站请求伪造 (CSRF)的保护。这种类型的攻击发生在恶意网站包含链接、表单按钮或一些 JavaScript 代码,这些代码旨在使用在浏览器中访问恶意网站的已登录用户的凭据,在您的网站上执行某些操作时。相关的攻击类型,“登录 CSRF”,攻击网站诱骗用户的浏览器使用其他人的凭据登录某个站点,也包括在内。
抵御 CSRF 攻击的第一道防线是确保 GET 请求(以及其他“安全”方法,如RFC 9110 第 9.2.1 节中所定义)没有副作用。然后可以通过如何使用 Django 的 CSRF 保护中概述的步骤来保护使用“不安全”方法(如 POST、PUT 和 DELETE)的请求。
工作原理¶
CSRF 保护基于以下内容:
一个 CSRF cookie,它是一个随机的秘密值,其他网站无法访问。
CsrfViewMiddleware
在调用django.middleware.csrf.get_token()
时,会将此 cookie 与响应一起发送。它也可以在其他情况下发送它。出于安全原因,每次用户登录时,秘密的值都会更改。一个名为“csrfmiddlewaretoken”的隐藏表单字段,存在于所有传出的 POST 表单中。
为了防止BREACH攻击,此字段的值不仅仅是秘密。它使用掩码在每次响应中都会被不同的方式加密。掩码在每次调用
get_token()
时都会随机生成,因此表单字段的值每次都不同。这部分由
csrf_token
模板标签完成。对于所有不使用 HTTP GET、HEAD、OPTIONS 或 TRACE 的传入请求,必须存在 CSRF cookie,并且“csrfmiddlewaretoken”字段必须存在且正确。如果不存在,用户将收到 403 错误。
验证“csrfmiddlewaretoken”字段值时,只将秘密(而不是完整的令牌)与 cookie 值中的秘密进行比较。这允许使用不断变化的令牌。虽然每个请求都可能使用自己的令牌,但秘密对所有令牌都是通用的。
此检查由
CsrfViewMiddleware
完成。CsrfViewMiddleware
验证浏览器提供的Origin 标头(如果提供),是否与当前主机和CSRF_TRUSTED_ORIGINS
设置相匹配。这提供了针对跨子域攻击的保护。此外,对于 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.com
和api.example.com
的 POST 请求。如果没有设置此设置,则 Referer 必须与 HTTPHost
标头匹配。可以使用
CSRF_TRUSTED_ORIGINS
设置来扩展超出当前主机或 cookie 域的已接受 Referer。
这确保只有来自可信域的表单才能用于将数据 POST 回来。
它故意忽略 GET 请求(以及RFC 9110 第 9.2.1 节中定义为“安全”的其他请求)。这些请求永远不应该有任何潜在的危险副作用,因此使用 GET 请求的 CSRF 攻击应该无害。RFC 9110 第 9.2.1 节将 POST、PUT 和 DELETE 定义为“不安全”,为了最大限度地保护,所有其他方法也被假定为不安全。
CSRF 保护无法防止中间人攻击,因此请使用HTTPS和HTTP 严格传输安全 (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 中已更改添加了包装异步视图函数的支持。
- ensure_csrf_cookie(view)¶
此装饰器强制视图发送 CSRF cookie。
Django 5.0 中已更改添加了包装异步视图函数的支持。
设置¶
许多设置可用于控制 Django 的 CSRF 行为
常见问题¶
Django 的 CSRF 保护默认情况下不与会话关联,这是一个问题吗?¶
不,这是设计使然。不将 CSRF 保护与会话关联允许在诸如允许匿名用户(没有会话)提交内容的粘贴板之类的站点上使用此保护。
如果希望将 CSRF 令牌存储在用户的会话中,请使用 CSRF_USE_SESSIONS
设置。
为什么用户登录后可能会遇到 CSRF 验证失败?¶
出于安全原因,每次用户登录时都会轮换 CSRF 令牌。登录前生成的包含表单的任何页面都将具有旧的、无效的 CSRF 令牌,需要重新加载。如果用户登录后使用后退按钮,或者在不同的浏览器标签页中登录,则可能会发生这种情况。