Skip to main content

跨站请求伪造保护

CSRF中间件和模板标签为 Cross Site Request Forgeries 提供了易于使用的保护。当恶意网站包含链接,表单按钮或某些JavaScript(旨在使用在其浏览器中访问恶意网站的登录用户的凭据在您的网站上执行某些操作)时,会发生此类型的攻击。还涉及相关类型的攻击,“登录CSRF”,其中攻击站点欺骗用户的浏览器登录具有他人的凭证的站点。

对CSRF攻击的第一个防御是确保GET请求(和其他“安全”方法,由 RFC 7231#section-4.2.1 定义)是副作用。通过“不安全”方法(例如POST,PUT和DELETE)的请求可以通过执行以下步骤来保护。

如何使用它

要在视图中利用CSRF保护,请按照下列步骤操作:

  1. 默认情况下,在 MIDDLEWARE 设置中激活CSRF中间件。如果你重写这个设置,记住 'django.middleware.csrf.CsrfViewMiddleware' 应该在任何视图中间件之前,假设已经处理CSRF攻击。

    如果禁用它(不推荐),则可以在要保护的特定视图上使用 csrf_protect() (请参阅下文)。

  2. 在使用POST表单的任何模板中,如果表单用于内部网址,请在 <form> 元素中使用 csrf_token 标记,例如:

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

    对于定位外部URL的POST表单,不应该这样做,因为这会导致CSRF令牌泄露,导致漏洞。

  3. 在相应的视图函数中,确保使用 RequestContext 呈现响应,以便 {% csrf_token %} 正常工作。如果你使用 render() 函数,通用视图或者contrib应用程序,你已经覆盖了,因为这些都使用 RequestContext

AJAX

虽然上述方法可以用于AJAX POST请求,但它有一些不便:您必须记住将CSRF令牌作为POST数据与每个POST请求一起传递。因此,有一个替代方法:在每个XMLHttpRequest上,将自定义 X-CSRFToken 头设置为CSRF令牌的值。这通常更容易,因为许多JavaScript框架提供了允许在每个请求上设置标头的钩子。

作为第一步,您必须获取CSRF令牌本身。推荐的令牌来源是 csrftoken cookie,如果您已为上述视图启用了CSRF保护,则将设置此Cookie。

注解

默认情况下,CSRF令牌cookie被命名为 csrftoken,但您可以通过 CSRF_COOKIE_NAME 设置控制cookie名称。

缺省情况下,CSRF标题名称为 HTTP_X_CSRFTOKEN,但您可以使用 CSRF_HEADER_NAME 设置对其进行自定义。

获取令牌很简单:

// using jQuery
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

上述代码可以通过使用 JavaScript Cookie库 来替换 getCookie 来简化:

var csrftoken = Cookies.get('csrftoken');

注解

CSRF令牌也存在于DOM中,但仅当在模板中使用 csrf_token 明确包括时。 cookie包含规范令牌; CsrfViewMiddleware 将喜欢cookie到DOM中的令牌。无论如何,如果令牌存在于DOM中,您可以保证有cookie,因此您应该使用cookie!

警告

如果您的视图未呈现包含 csrf_token 模板标记的模板,则Django可能不会设置CSRF令牌cookie。这在将表单动态添加到页面的情况下很常见。为了解决这种情况,Django提供了强制设置cookie的视图装饰器:ensure_csrf_cookie()

最后,您必须实际设置您的AJAX请求的头,同时保护CSRF令牌不被发送到其他域使用 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及更高版本,则只需使用Cookie和标头名称配置 $http 提供程序即可:

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

在Jinja2模板中使用CSRF

Django的 Jinja2 模板后端将 {{ csrf_input }} 添加到所有模板的上下文中,这相当于Django模板语言中的 {% csrf_token %}。例如:

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

装饰器方法

与其添加 CsrfViewMiddleware 作为毯式保护,您可以使用 csrf_protect 装饰器,其具有完全相同的功能,在需要保护的特定视图。必须对在输出中插入CSRF令牌的视图使用 ,并且在接受POST表单数据的那些视图上使用 。 (这些通常是相同的视图函数,但不总是)。

使用装饰器本身是 不建议,因为如果你忘记使用它,你将有一个安全漏洞。使用两者的“带和括号”策略是很好的,并且将产生最小的开销。

csrf_protect(view)

装饰员提供 CsrfViewMiddleware 的保护视图。

用法:

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

@csrf_protect
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)

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

已拒绝的请求

默认情况下,如果传入请求未能通过 CsrfViewMiddleware 执行的检查,则会向用户发送“403 Forbidden”响应。这通常只有当有一个真正的跨站点请求伪造,或由于编程错误,CSRF令牌没有包括在POST表单中时才会看到。

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

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

怎么运行的

CSRF保护基于以下事项:

  1. 基于随机秘密值的CSRF Cookie,其他网站将无法访问。

    此cookie由 CsrfViewMiddleware 设置。它与每个响应称为 django.middleware.csrf.get_token() (内部用于检索CSRF令牌的函数)一起发送,如果请求中没有设置它。

    为了防止 BREACH 攻击,令牌不仅仅是秘密;随机盐被添加到秘密,并用于加扰它。

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

  2. 所有传出POST表单中都有一个名为“csrfmiddlewaretoken”的隐藏表单字段。该字段的值同样是秘密的值,其中一个盐被添加到它并用于加扰它。每次调用 get_token() 时都会重新生成盐,以便在每个此类响应中都更改表单字段值。

    此部分由模板标记完成。

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

    当验证“csrfmiddlewaretoken”字段值时,只有秘密,而不是完整令牌,与Cookie值中的秘密进行比较。这允许使用不断变化的令牌。虽然每个请求可以使用其自己的令牌,但是秘密对于所有人来说是共同的。

    此检查由 CsrfViewMiddleware 完成。

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

    这也解决了一个中间人攻击,当使用会话独立秘密时,由于HTTP Set-Cookie 头部(不幸的是)被客户端接受,即使他们正在与HTTPS下的站点通信,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 7231 定义为“安全”的其他请求)。这些请求不应该有任何潜在的危险副作用,因此具有GET请求的CSRF攻击应该是无害的。 RFC 7231 将POST,PUT和DELETE定义为“不安全”,并且所有其他方法也被假定为不安全,以获得最大保护。

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

Changed in Django 1.9:

添加了针对 CSRF_COOKIE_DOMAIN 设置的检查。

Changed in Django 1.10:

向令牌添加了盐化,并开始更改每个请求以防止 BREACH 攻击。

缓存

如果 csrf_token 模板标签被模板使用(或者 get_token 函数被称为某种其他方式),CsrfViewMiddleware 将在响应中添加一个cookie和一个 Vary: Cookie 头。这意味着如果中间件按照指示使用(UpdateCacheMiddleware 在所有其他中间件之前),则中间件将与高速缓存中间件良好地协作。

但是,如果在单个视图上使用缓存装饰器,CSRF中间件将无法设置Vary头或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 装饰器,使他们不再拒绝请求。在每个其他方面(例如发送cookie等),它们的行为相同。

如果由于某种原因,您 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.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

@csrf_exempt
def my_view(request):
    return HttpResponse('Hello world')
requires_csrf_token(view)

通常,如果 CsrfViewMiddleware.process_view 或等效的 csrf_protect 未运行,则 csrf_token 模板标记将无法工作。视图装饰程序 requires_csrf_token 可用于确保模板标记正常工作。此装饰器与 csrf_protect 类似,但不会拒绝传入的请求。

例:

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

@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_view未使用

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

解决方案:使用 requires_csrf_token()

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

可能有一些视图未受保护,并已被 csrf_exempt 豁免,但仍需要包括CSRF令牌。

解决方案:使用 csrf_exempt() 后跟 requires_csrf_token()。 (即 requires_csrf_token 应该是最内部的装饰器)。

View需要保护一个路径

视图仅需要一组条件下的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发出POST请求,该页面没有带有 csrf_token 的HTML表单,该表单会导致发送所需的CSRF Cookie。

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

Contrib和可重复使用的应用程序

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

经常问的问题

发布任意CSRF令牌对(Cookie和POST数据)漏洞?

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

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

是Django的CSRF保护没有链接到会话的问题的事实吗?

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

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

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