跨站点请求伪造防护

CSRF中间件和模板标签提供针对跨站请求伪造的易用保护。 当恶意网站包含链接,表单按钮或一些旨在在您的网站上执行某些操作的JavaScript时,会使用在浏览器中访问恶意网站的登录用户的凭据进行此类攻击。 还涉及到一种相关的攻击类型,“登录CSRF”,攻击网站欺骗用户的浏览器,使用其他人的凭证登录网站。

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

如何使用

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

  1. CSRF中间件默认在MIDDLEWARE设置中激活。 If you override that setting, remember that 'django.middleware.csrf.CsrfViewMiddleware' should come before any view middleware that assume that CSRF attacks have been dealt with.

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

  2. 在任何使用POST表单的模板中,如果表单用于内部URL,则使用<form>元素内的csrf_token标记。

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

    这不应该针对定位外部URL的POST表单来完成,因为这会导致CSRF令牌被泄露,从而导致漏洞。

  3. 在相应的视图函数中,确保使用RequestContext来呈现响应,以便{% csrf_token %} 将正常工作。 如果您正在使用render()函数,通用视图或contrib应用程序,那么您已经使用了RequestContext

AJAX ¶ T0>

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

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

如果CSRF_USE_SESSIONSFalse

推荐的令牌来源是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明确包含。 The cookie contains the canonical token; the CsrfViewMiddleware will prefer the cookie to the token in the DOM. 无论如何,如果令牌存在于DOM中,则可以保证有cookie,所以您应该使用cookie!

警告

如果您的视图不是渲染包含csrf_token模板标签的模板,那么Django可能不会设置CSRF标记cookie。 在表单动态添加到页面的情况下,这是很常见的。 为了解决这个问题,Django提供了一个强制设置cookie的视图装饰器:ensure_csrf_cookie()

如果CSRF_USE_SESSIONSTrue

如果激活CSRF_USE_SESSIONS,则必须在您的HTML中包含CSRF标记,并使用JavaScript从DOM读取标记:

{% csrf_token %}
<script type="text/javascript">
// using jQuery
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
</script>

在AJAX请求上设置标记

最后,你必须在你的AJAX请求中设置标题,同时在jQuery 1.5.1和更新版本中使用settings.crossDomain保护CSRF标记不被发送到其他域。

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表单数据的视图必须同时使用 (这些往往是相同的查看功能,但并不总是)。

Use of the decorator by itself is not recommended, since if you forget to use it, you will have a security hole. 使用这两种方法的“腰带和大括号”策略是很好的,并且会产生最小的开销。

csrf_protect T0>(视图 T1>)¶ T2>

提供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 failures are logged as warnings to the django.security.csrf logger.

在Django 1.11中更改:

在旧版本中,CSRF失败记录到django.request记录器。

它如何工作

CSRF保护基于以下几点:

  1. 基于随机秘密值的CSRF Coo​​kie,其他站点无法访问。

    这个cookie由CsrfViewMiddleware设置。 如果它没有在请求中设置,它将与每个调用了django.middleware.csrf.get_token()的响应一起发送(在内部用于检索CSRF标记的函数)。

    In order to protect against BREACH attacks, the token is not simply the secret; a random salt is prepended to the secret and used to scramble it.

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

  2. 在所有传出的POST表单中都有一个名为“csrfmiddlewaretoken”的隐藏表单域。 这个领域的价值也是秘密的价值,加上一个盐,用来加密它。 在每次调用get_token()时都会重新生成salt,以便在每个响应中更改表单字段值。

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

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

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

    这个检查是由CsrfViewMiddleware完成的。

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

    由于HTTP Set-Cookie头部(不幸地)被客户端接受,所以这也解决了在使用会话独立秘密时在HTTPS下可能发生的中间人攻击正在与HTTPS下的网站交谈。 (Referer checking is not done for HTTP requests because the presence of the Referer header isn’t reliable enough under HTTP.)

    如果设置了CSRF_COOKIE_DOMAIN设置,则将引用者与它进行比较。 此设置支持子域。 For example, CSRF_COOKIE_DOMAIN = '.example.com' will allow POST requests from www.example.com and api.example.com. 如果设置没有设置,则引用者必须匹配HTTP Host标头。

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

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

它故意忽略GET请求(以及 RFC 7231定义为“安全”的其他请求)。 这些请求不应该有任何潜在的危险的副作用,所以CSRF攻击与GET请求应该是无害的。 RFC 7231 defines POST, PUT, and DELETE as ‘unsafe’, and all other methods are also assumed to be unsafe, for maximum protection.

CSRF保护不能防止中间人攻击,因此使用HTTPSHTTP Strict Transport Security 它还假定validation of the HOST header,并且在您的站点上没有任何cross-site scripting vulnerabilities(因为XSS漏洞已经让攻击者执行任何CSRF漏洞允许和更糟)。

删除Referer标题

为避免将引荐来源网址泄露给第三方网站,您可能需要在您的网站的<a>标签上禁用引荐来源 例如,您可以使用&lt; meta name =“referrer” content =“no-referrer”&gt; t0>标签或包含 引荐-政策: 没有引用 头。 由于CSRF保护严格的引用者对HTTPS请求进行检查,这些技术会对具有“不安全”方法的请求造成CSRF失败。 请改为使用&lt; a rel =“noreferrer” ...&gt;“第三方网站。

缓存¶ T0>

If the csrf_token template tag is used by a template (or the get_token function is called some other way), CsrfViewMiddleware will add a cookie and a 变化: 曲奇饼 标题的响应。 这意味着,如果中间件按照指示使用(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

测试¶ T0>

The CsrfViewMiddleware will usually be a big hindrance to testing view functions, due to the need for the CSRF token which must be sent with every POST request. 出于这个原因,Django的测试HTTP客户端已被修改,以设置一个放宽中间件和csrf_protect装饰器的请求标志,以便它们不再拒绝请求。 在其他方面(例如发送cookies等),它们的行为都是一样的。

If, for some reason, you want the test client to perform CSRF checks, you can create an instance of the test client that enforces CSRF checks:

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

限制¶ T0>

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

边缘情况

某些意见可能有不寻常的要求,这意味着它们不符合这里设想的正常模式。 在这些情况下,许多公用设施都可以使用。 他们可能需要的场景在下一节中描述。

应用¶ T0>

下面的示例假定您正在使用基于功能的视图。 如果您正在使用基于类的视图,则可以参考Decorating class-based views

csrf_exempt(view)[source]

这个装饰器标志着一个视图被免除了中间件保证的保护。 例:

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 T0>(视图 T1>)¶ T2>

通常,如果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。

场景¶ T0>

只有几个视图禁用CSRF保护

大多数意见要求CSRF保护,但有一些则不。

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

CsrfViewMiddleware.process_view未使用

There are cases when CsrfViewMiddleware.process_view may not have run before your view is run - 404 and 500 handlers, for example - but you still need the CSRF token in a form.

解决方案:使用requires_csrf_token()

不受保护的视图需要CSRF标记

There may be some views that are unprotected and have been exempted by csrf_exempt, but still need to include the CSRF token.

解决方案:使用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发出一个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_USE_SESSIONS设置。

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

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