跨站点请求伪造保护

CSRF中间件和模板标签提供了易于使用的保护 Cross Site Request Forgeries . 当恶意网站包含一个链接、一个表单按钮或一些javascript,打算使用登录用户在浏览器中访问恶意网站的凭据在您的网站上执行某些操作时,就会发生这种攻击。还包括一种相关的攻击类型“登录CSRF”,即攻击站点诱使用户的浏览器使用他人的凭据登录站点。

针对CSRF攻击的第一道防线是确保获取请求(以及其他“安全”方法,如 RFC 7231#section-4.2.1 )无副作用。通过“不安全”方法(如POST、PUT和DELETE)的请求可以通过以下步骤进行保护。

如何使用它

要在您的视图中利用CSRF保护,请执行以下步骤:

  1. CSRF中间件默认在 MIDDLEWARE 设置。如果您覆盖该设置,请记住 'django.middleware.csrf.CsrfViewMiddleware' 应该先于任何假设已经处理了CSRF攻击的视图中间件。

    如果您禁用了它,这是不推荐的,您可以使用 csrf_protect() 关于您想要保护的特定视图(见下文)。

  2. 在任何使用Post表单的模板中,使用 csrf_token 标签内 <form> 元素,如果表单用于内部URL,例如:

    <form method="post">{% csrf_token %}
    

    对于以外部URL为目标的POST表单,不应该这样做,因为这样会导致CSRF令牌泄漏,从而导致漏洞。

  3. 在相应的视图函数中,确保 RequestContext 用于呈现响应,以便 {{% csrf_token %}} 会正常工作。如果你在使用 render() 函数、通用视图或控件应用程序,因为这些都使用 RequestContext .

AJAX

虽然上面的方法可以用于Ajax Post请求,但它有一些不便之处:您必须记住在每个Post请求中都将CSRF令牌作为Post数据传入。因此,有一个替代方法:在每个xmlhttpRequest上,设置一个自定义的 X-CSRFToken 标题(由 CSRF_HEADER_NAME 设置)CSRF令牌的值。这通常更容易,因为许多JavaScript框架提供了钩子,允许在每个请求上设置头。

首先,您必须获得CSRF令牌。如何做到这一点取决于 CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY 设置已启用。

在Ajax请求上设置令牌

最后,您必须在Ajax请求上实际设置头,同时使用 settings.crossDomain 在jquery 1.5.1和更新版本中:

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

如果您使用的是AngularJS 1.1.3及更高版本,则足以配置 $http 具有cookie和头名称的提供程序:

$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';

在Jinja2模板中使用CSRF

Django Jinja2 模板后端添加 {{{{ csrf_input }}}} 在所有模板的上下文中, {{% csrf_token %}} 使用django模板语言。例如:

<form method="post">{{ csrf_input }}

装饰方法

而不是添加 CsrfViewMiddleware 作为毯子保护,您可以使用 csrf_protect decorator在需要保护的特定视图上具有完全相同的功能。必须使用 both 对于在输出中插入CSRF令牌的视图,以及那些接受POST表单数据的视图。(这些通常是相同的视图功能,但并非总是如此)。

修饰符本身的使用是 未推荐的 ,因为如果你忘记使用它,你会有一个安全漏洞。使用这两种方法的“皮带和支持”策略都很好,并且将产生最小的开销。

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)

如果使用的是基于类的视图,则可以参考 Decorating class-based views .

拒绝的请求

默认情况下,如果传入请求未能通过由执行的检查,将向用户发送“403禁止”响应。 CsrfViewMiddleware . 这通常只能在存在真正的跨站点请求伪造时看到,或者,由于编程错误,CSRF令牌未包含在POST表单中时看到。

但是,错误页面不是很友好,因此您可能需要提供自己的视图来处理这种情况。为此,只需设置 CSRF_FAILURE_VIEW 设置。

CSRF故障作为警告记录到 django.security.csrf 记录器。

它是如何工作的

CSRF保护基于以下内容:

  1. 一个基于随机秘密值的CSRF cookie,其他站点将无法访问该秘密值。

    此cookie由设置 CsrfViewMiddleware . 它与已调用的每个响应一起发送 django.middleware.csrf.get_token() (在内部用于检索CSRF令牌的函数),如果尚未根据请求进行设置。

    为了防止 BREACH 攻击时,令牌不仅仅是秘密;一个随机的盐是预先准备好的秘密,并用来扰乱它。

    出于安全原因,每次用户登录时都会更改机密的值。

  2. 所有传出的邮件表单中都存在名为“csrfmiddlewaretoken”的隐藏表单字段。这个领域的价值,再一次,是秘密的价值,加上一种盐,既加在它上面,又用来扰乱它。每次调用时盐都会重新生成 get_token() 这样表单字段值在每个这样的响应中都会发生更改。

    此部分由模板标记完成。

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

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

    这张支票是由 CsrfViewMiddleware .

  4. 此外,对于HTTPS请求,严格的引用检查由 CsrfViewMiddleware . 这意味着,即使子域可以在您的域上设置或修改cookie,也不能强制用户发布到您的应用程序,因为该请求不会来自您自己的确切域。

    这也解决了一个中间人在使用会话独立的秘密时,在HTTPS下可能受到的攻击,因为HTTP Set-Cookie 不幸的是,客户机甚至在使用HTTPS与站点通信时也会接受报头。(没有对HTTP请求执行引用检查,因为存在 Referer 头在HTTP下不够可靠。)

    如果 CSRF_COOKIE_DOMAIN 设置好后,将参照者与其进行比较。此设置支持子域。例如, CSRF_COOKIE_DOMAIN = '.example.com' 将允许来自的投递请求 www.example.comapi.example.com . 如果未设置该设置,则引用者必须与HTTP匹配 Host 标题。

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

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

它故意忽略GET请求(以及由定义为“安全”的其他请求) RFC 7231 )这些请求不应该有任何潜在的危险副作用,因此带有GET请求的CSRF攻击应该是无害的。 RFC 7231 将POST、PUT和DELETE定义为“不安全”,并且所有其他方法也被假定为不安全,以获得最大的保护。

CSRF保护无法抵御中间人攻击,因此使用 HTTPS 具有 HTTP严格传输安全 . 它也假定 validation of the HOST header 而且没有 cross-site scripting vulnerabilities 在您的站点上(因为XSS漏洞已经让攻击者做任何CSRF漏洞允许的事情,而且更糟)。

去掉 Referer 页眉

为了避免向第三方网站公开引用URL,您可能需要 disable the referer 在你的网站上 <a> 标签。例如,您可以使用 <meta name="referrer" content="no-referrer"> 标记或包括 Referrer-Policy: no-referrer 标题。由于CSRF保护对HTTPS请求进行严格的引用检查,这些技术会导致使用“不安全”方法的请求出现CSRF故障。相反,使用诸如 <a rel="noreferrer" ...>" 用于链接到第三方网站。

高速缓存

如果 csrf_token 模板标记由模板(或 get_token 函数被称为其他方法)。 CsrfViewMiddleware 将添加一个cookie和一个 Vary: Cookie 响应的标题。这意味着如果按照指示使用,中间件将与缓存中间件一起发挥良好的作用。 (UpdateCacheMiddleware 先于所有其他中间件)。

但是,如果在单个视图上使用缓存修饰符,那么CSRF中间件还不能设置vary头或CSRF cookie,并且响应将在没有任何一个头或CSRF cookie的情况下进行缓存。在这种情况下,对于需要插入CSRF令牌的任何视图,应使用 django.views.decorators.csrf.csrf_protect() 先装饰:

from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_protect

@cache_page(60 * 15)
@csrf_protect
def my_view(request):
    ...

如果使用的是基于类的视图,则可以参考 Decorating class-based views .

测试

这个 CsrfViewMiddleware 通常会对测试视图功能造成很大的阻碍,因为需要CSRF令牌,必须随每个POST请求一起发送。出于这个原因,Django的测试HTTP客户机被修改为在请求上设置一个标志,使中间件和 csrf_protect 这样他们就不再拒绝请求了。在其他方面(例如发送cookies等),他们的行为都是一样的。

如果,出于某种原因,你 want 测试客户机要执行CSRF检查,可以创建执行CSRF检查的测试客户机实例:

>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)

局限性

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

边缘案例

某些视图可能有不寻常的要求,这意味着它们不符合这里设想的正常模式。在这些情况下,许多实用程序都是有用的。下一节描述了可能需要它们的场景。

公用事业

下面的示例假设您使用的是基于函数的视图。如果您使用的是基于类的视图,则可以参考 Decorating class-based views .

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')
requires_csrf_token(view)

通常 csrf_token 如果 CsrfViewMiddleware.process_view 或者类似的 csrf_protect 还没有运行。视图修饰符 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)

此修饰符强制视图发送CSRF cookie。

情节

CSRF保护只应在几个视图中禁用

大多数视图需要CSRF保护,但少数视图不需要。

解决方案:而不是禁用中间件并应用 csrf_protect 对于所有需要它的视图,启用中间件并使用 csrf_exempt() .

csrfviewmiddleware.process_视图未使用

有些情况下 CsrfViewMiddleware.process_view 可能在运行视图之前没有运行过(例如404和500个处理程序),但您仍然需要表单中的CSRF令牌。

解决方案:使用 requires_csrf_token()

未受保护的视图需要CSRF令牌

某些视图可能未受保护,并且已被 csrf_exempt ,但仍需要包含CSRF令牌。

解决方案:使用 csrf_exempt() 然后 requires_csrf_token() . (即 requires_csrf_token 应该是最里面的修饰符)。

视图需要一条路径的保护

一个视图只需要在一组条件下进行CSRF保护,并且不能在剩余时间内使用。

解决方案:使用 csrf_exempt() 对于整个视图功能,以及 csrf_protect() 对于其中需要保护的路径。例子::

from django.views.decorators.csrf import csrf_exempt, csrf_protect

@csrf_exempt
def my_view(request):

    @csrf_protect
    def protected_path(request):
        do_something()

    if some_condition():
       return protected_path(request)
    else:
       do_something_else()

页面使用Ajax而不使用任何HTML表单

页面通过Ajax发出发布请求,而页面没有带有 csrf_token 这将导致发送所需的CSRF cookie。

解决方案:使用 ensure_csrf_cookie() 在发送页面的视图上。

控制和可重用应用程序

因为开发者可以关闭 CsrfViewMiddleware ,contrib应用程序中的所有相关视图都使用 csrf_protect 以确保这些应用程序对CSRF的安全性。建议其他需要相同保证的可重用应用程序的开发人员也使用 csrf_protect 装饰他们的观点。

常见问题

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

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

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

Django的CSRF保护在默认情况下没有链接到会话是一个问题吗?

不,这是按设计的。不将CSRF保护链接到会话允许在诸如 pastebin 允许来自没有会话的匿名用户的提交。

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

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

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