Proteção Cross Site Request Forgery

A middleware e tag de template Csrf fornecem uma proteção fácil de usar contra Requisições Cross Site falsas. Esse tipo de ataque ocorre quando um website malicioso cria um link ou um botão de formulário que é destinado a executar alguma ação sobre seu site, usando credenciais de um usuário logado que pode ser enganado ao clicar em um link no seu navegador. 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.

A primeira defesa contra ataques CSRF é assegurar que requisições GET são livres de efeitos colaterais. Requisições POST podem então ser protegidas por este middleware, adicionando-o em sua lista de middlewares instalados.

Novo no Django 1.2: The ‘contrib’ apps, including the admin, use the functionality described here. Because it is security related, a few things have been added to core functionality to allow this to happen without any required upgrade steps.

Como usá-lo

Alterado no Django 1.2: The template tag functionality (the recommended way to use this) was added in version 1.2. The previous method (still available) is described under Legacy method.

To enable CSRF protection for your views, follow these steps:

  1. Add the middleware 'django.middleware.csrf.CsrfViewMiddleware' to your list of middleware classes, MIDDLEWARE_CLASSES. (It should come before CsrfResponseMiddleware if that is being used, and before any view middleware that assume that CSRF attacks have been dealt with.)

    Alternatively, you can use the decorator django.views.decorators.csrf.csrf_protect on particular views you want to protect (see below).

  2. In any template that uses a POST form, use the csrf_token tag inside the <form> element if the form is for an internal URL, e.g.:

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

    This should not be done for POST forms that target external URLs, since that would cause the CSRF token to be leaked, leading to a vulnerability.

  3. In the corresponding view functions, ensure that the 'django.core.context_processors.csrf' context processor is being used. Usually, this can be done in one of two ways:

    1. Use RequestContext, which always uses 'django.core.context_processors.csrf' (no matter what your TEMPLATE_CONTEXT_PROCESSORS setting). If you are using generic views or contrib apps, you are covered already, since these apps use RequestContext throughout.

    2. Manually import and use the processor to generate the CSRF token and add it to the template context. e.g.:

      from django.core.context_processors import csrf
      from django.shortcuts import render_to_response
      
      def my_view(request):
          c = {}
          c.update(csrf(request))
          # ... view code here
          return render_to_response("a_template.html", c)
      

      You may want to write your own render_to_response wrapper that takes care of this step for you.

The utility script extras/csrf_migration_helper.py can help to automate the finding of code and templates that may need to be upgraded. It contains full help on how to use it.

AJAX

While the above method can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. This is often easier, because many javascript frameworks provide hooks that allow headers to be set on every request. In jQuery, you can use the ajaxSend event as follows:

$(document).ajaxSend(function(event, xhr, settings) {
    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;
    }
    function sameOrigin(url) {
        // url could be relative or scheme relative or absolute
        var host = document.location.host; // host + port
        var protocol = document.location.protocol;
        var sr_origin = '//' + host;
        var origin = protocol + sr_origin;
        // Allow absolute or scheme relative URLs to same origin
        return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
            (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
            // or any other URL that isn't scheme relative or absolute i.e relative.
            !(/^(\/\/|http:|https:).*/.test(url));
    }
    function safeMethod(method) {
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }

    if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
        xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
    }
});

Note

Due to a bug introduced in jQuery 1.5, the example above will not work correctly on that version. Make sure you are running at least jQuery 1.5.1.

Adding this to a javascript file that is included on your site will ensure that AJAX POST requests that are made via jQuery will not be caught by the CSRF protection.

The decorator method

Rather than adding CsrfViewMiddleware as a blanket protection, you can use the csrf_protect decorator, which has exactly the same functionality, on particular views that need the protection. It must be used both on views that insert the CSRF token in the output, and on those that accept the POST form data. (These are often the same view function, but not always). It is used like this:

from django.views.decorators.csrf import csrf_protect
from django.template import RequestContext

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

Use of the decorator is not recommended by itself, since if you forget to use it, you will have a security hole. The 'belt and braces' strategy of using both is fine, and will incur minimal overhead.

Legacy method

In Django 1.1, the template tag did not exist. Instead, a post-processing middleware that re-wrote POST forms to include the CSRF token was used. If you are upgrading a site from version 1.1 or earlier, please read this section and the Upgrading notes below. The post-processing middleware is still available as CsrfResponseMiddleware, and it can be used by following these steps:

  1. Follow step 1 above to install CsrfViewMiddleware.

  2. Add 'django.middleware.csrf.CsrfResponseMiddleware' to your MIDDLEWARE_CLASSES setting.

    CsrfResponseMiddleware needs to process the response before things like compression or setting ofETags happen to the response, so it must come after GZipMiddleware, CommonMiddleware and ConditionalGetMiddleware in the list. It also must come after CsrfViewMiddleware.

Use of the CsrfResponseMiddleware is not recommended because of the performance hit it imposes, and because of a potential security problem (see below). It can be used as an interim measure until applications have been updated to use the csrf_token tag. It is deprecated and will be removed in Django 1.4.

Django 1.1 and earlier provided a single CsrfMiddleware class. This is also still available for backwards compatibility. It combines the functions of the two middleware.

Note also that previous versions of these classes depended on the sessions framework, but this dependency has now been removed, with backward compatibility support so that upgrading will not produce any issues.

Security of legacy method

The post-processing CsrfResponseMiddleware adds the CSRF token to all POST forms (unless the view has been decorated with csrf_response_exempt). If the POST form has an external untrusted site as its target, rather than an internal page, that site will be sent the CSRF token when the form is submitted. Armed with this leaked information, that site will then be able to successfully launch a CSRF attack on your site against that user. The @csrf_response_exempt decorator can be used to fix this, but only if the page doesn't also contain internal forms that require the token.

Upgrading notes

When upgrading to version 1.2 or later, you may have applications that rely on the old post-processing functionality for CSRF protection, or you may not have enabled any CSRF protection. This section outlines the steps necessary for a smooth upgrade, without having to fix all the applications to use the new template tag method immediately.

First of all, the location of the middleware and related functions have changed. There are backwards compatible stub files so that old imports will continue to work for now, but they are deprecated and will be removed in Django 1.4. The following changes have been made:

  • Middleware have been moved to django.middleware.csrf
  • Decorators have been moved to django.views.decorators.csrf
Old New
django.contrib.csrf.middleware.CsrfMiddleware django.middleware.csrf.CsrfMiddleware
django.contrib.csrf.middleware.CsrfViewMiddleware django.middleware.csrf.CsrfViewMiddleware
django.contrib.csrf.middleware.CsrfResponseMiddleware django.middleware.csrf.CsrfResponseMiddleware
django.contrib.csrf.middleware.csrf_exempt django.views.decorators.csrf.csrf_exempt
django.contrib.csrf.middleware.csrf_view_exempt django.views.decorators.csrf.csrf_view_exempt
django.contrib.csrf.middleware.csrf_response_exempt django.views.decorators.csrf.csrf_response_exempt

You should update any imports, and also the paths in your MIDDLEWARE_CLASSES.

If you have CsrfMiddleware in your MIDDLEWARE_CLASSES, you will now have a working installation with CSRF protection. It is recommended at this point that you replace CsrfMiddleware with its two components, CsrfViewMiddleware and CsrfResponseMiddleware (in that order).

If you do not have any of the middleware in your MIDDLEWARE_CLASSES, you will have a working installation but without any CSRF protection for your views (just as you had before). It is strongly recommended to install CsrfViewMiddleware and CsrfResponseMiddleware, as described above.

Note that contrib apps, such as the admin, have been updated to use the csrf_protect decorator, so that they are secured even if you do not add the CsrfViewMiddleware to your settings. However, if you have supplied customised templates to any of the view functions of contrib apps (whether explicitly via a keyword argument, or by overriding built-in templates), you MUST update them to include the csrf_token template tag as described above, or they will stop working. (If you cannot update these templates for some reason, you will be forced to use CsrfResponseMiddleware for these views to continue working).

Note also, if you are using the comments app, and you are not going to add CsrfViewMiddleware to your settings (not recommended), you will need to add the csrf_protect decorator to any views that include the comment forms and target the comment views (usually using the comment_form_target template tag).

Assuming you have followed the above, all views in your Django site will now be protected by the CsrfViewMiddleware. Contrib apps meet the requirements imposed by the CsrfViewMiddleware using the template tag, and other applications in your project will meet its requirements by virtue of the CsrfResponseMiddleware.

The next step is to update all your applications to use the template tag, as described in Como usá-lo, steps 2-3. This can be done as soon as is practical. Any applications that are updated will now require Django 1.1.2 or later, since they will use the CSRF template tag which was not available in earlier versions. (The template tag in 1.1.2 is actually a no-op that exists solely to ease the transition to 1.2 — it allows apps to be created that have CSRF protection under 1.2 without requiring users of the apps to upgrade to the Django 1.2.X series).

The utility script extras/csrf_migration_helper.py can help to automate the finding of code and templates that may need to be upgraded. It contains full help on how to use it.

Finally, once all applications are upgraded, CsrfResponseMiddleware can be removed from your settings.

While CsrfResponseMiddleware is still in use, the csrf_response_exempt decorator, described in Exceptions, may be useful. The post-processing middleware imposes a performance hit and a potential vulnerability, and any views that have been upgraded to use the new template tag method no longer need it.

Exceptions

Alterado no Django 1.2: Import paths for the decorators below were changed.

To manually exclude a view function from being handled by either of the two CSRF middleware, you can use the csrf_exempt decorator, found in the django.views.decorators.csrf module. For example:

from django.views.decorators.csrf import csrf_exempt

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

Like the middleware, the csrf_exempt decorator is composed of two parts: a csrf_view_exempt decorator and a csrf_response_exempt decorator, found in the same module. These disable the view protection mechanism (CsrfViewMiddleware) and the response post-processing (CsrfResponseMiddleware) respectively. They can be used individually if required.

Subdomains

By default, CSRF cookies are specific to the subdomain they are set for. This means that a form served from one subdomain (e.g. server1.example.com) will not be able to have a target on another subdomain (e.g. server2.example.com). This restriction can be removed by setting CSRF_COOKIE_DOMAIN to be something like ".example.com".

Please note that, with or without use of this setting, this CSRF protection mechanism is not safe against cross-subdomain attacks -- see Limitations.

Rejected requests

By default, a '403 Forbidden' response is sent to the user if an incoming request fails the checks performed by CsrfViewMiddleware. This should usually only be seen when there is a genuine Cross Site Request Forgery, or when, due to a programming error, the CSRF token has not been included with a POST form.

No logging is done, and the error message is not very friendly, so you may want to provide your own page for handling this condition. To do this, simply set the CSRF_FAILURE_VIEW setting to a dotted path to your own view function, which should have the following signature:

def csrf_failure(request, reason="")

where reason is a short message (intended for developers or logging, not for end users) indicating the reason the request was rejected.

How it works

The CSRF protection is based on the following things:

  1. A CSRF cookie that is set to a random value (a session independent nonce, as it is called), which other sites will not have access to.

    This cookie is set by CsrfViewMiddleware. It is meant to be permanent, but since there is no way to set a cookie that never expires, it is sent with every response that has called django.middleware.csrf.get_token() (the function used internally to retrieve the CSRF token).

  2. A hidden form field with the name 'csrfmiddlewaretoken' present in all outgoing POST forms. The value of this field is the value of the CSRF cookie.

    This part is done by the template tag (and with the legacy method, it is done by CsrfResponseMiddleware).

  3. For all incoming POST requests, a CSRF cookie must be present, and the 'csrfmiddlewaretoken' field must be present and correct. If it isn't, the user will get a 403 error.

    This check is done by CsrfViewMiddleware.

  4. In addition, for HTTPS requests, strict referer checking is done by CsrfViewMiddleware. This is necessary to address a Man-In-The-Middle attack that is possible under HTTPS when using a session independent nonce, due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted by clients that are talking to a site under HTTPS. (Referer checking is not done for HTTP requests because the presence of the Referer header is not reliable enough under HTTP.)

This ensures that only forms that have originated from your Web site can be used to POST data back.

It deliberately only targets HTTP POST requests (and the corresponding POST forms). GET requests ought never to have any potentially dangerous side effects (see 9.1.1 Safe Methods, HTTP 1.1, RFC 2616), and so a CSRF attack with a GET request ought to be harmless.

CsrfResponseMiddleware checks the Content-Type before modifying the response, and only pages that are served as 'text/html' or 'application/xml+xhtml' are modified.

Caching

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 Vary: Cookie header to the response. Similarly, CsrfResponseMiddleware will send the Vary: Cookie header if it inserted a token. This means that these middleware will play well with the cache middleware if it is used as instructed (UpdateCacheMiddleware goes before all other middleware).

However, if you use cache decorators on individual views, the CSRF middleware will not yet have been able to set the Vary header or the CSRF cookie, and the response will be cached without either one. In this case, on any views that will require a CSRF token to be inserted you should use the django.views.decorators.csrf.csrf_protect() decorator first:

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):
    # ...

Testing

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. For this reason, Django's HTTP client for tests has been modified to set a flag on requests which relaxes the middleware and the csrf_protect decorator so that they no longer rejects requests. In every other respect (e.g. sending cookies etc.), they behave the same.

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)

Limitations

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.

If you are using CsrfResponseMiddleware and your app creates HTML pages and forms in some unusual way, (e.g. it sends fragments of HTML in JavaScript document.write statements) you might bypass the filter that adds the hidden field to the form, in which case form submission will always fail. You should use the template tag or django.middleware.csrf.get_token() to get the CSRF token and ensure it is included when your form is submitted.

Contrib and reusable apps

Because it is possible for the developer to turn off the CsrfViewMiddleware, all relevant views in contrib apps use the csrf_protect decorator to ensure the security of these applications against CSRF. It is recommended that the developers of other reusable apps that want the same guarantees also use the csrf_protect decorator on their views.