Подделка межсайтового запроса (CSRF)

Промежуточный слой CSRF и шаблонный тег предоставляют легкую-в-использовании защиту против Межсайтовой подделки запроса. Этот тип атак случается, когда злонамеренный Web сайт содержит ссылку, кнопку формы или некоторый javascript, который предназначен для выполнения некоторых действий на вашем Web сайте, используя учетные данные авторизованного пользователя, который посещал злонамеренный сайт в своем браузере. Сюда также входит связанный тип атак, „login CSRF“, где атакуемый сайт обманывает браузер пользователя, авторизируясь на сайте с чужими учетными данными.

The first defense against CSRF attacks is to ensure that GET requests (and other „safe“ methods, as defined by RFC 7231#section-4.2.1) are side effect free. Requests via „unsafe“ methods, such as POST, PUT, and DELETE, can then be protected by following the steps below.

Как это использовать

Для того чтобы включить CSRF защиту для ваших представлений, выполните следующие шаги:

  1. The CSRF middleware is activated by default in the MIDDLEWARE setting. 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. 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 method="post">{% csrf_token %}
    

    Это не должно делаться для POST форм, которые ссылаются на внешние URL’ы, поскольку это может вызвать утечку CSRF токена, что приводит к уязвимости.

  3. In the corresponding view functions, ensure that RequestContext is used to render the response so that {% csrf_token %} will work properly. If you’re using the render() function, generic views, or contrib apps, you are covered already since these all use RequestContext.

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 (as specified by the CSRF_HEADER_NAME setting) 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.

First, you must get the CSRF token. How to do that depends on whether or not the CSRF_USE_SESSIONS and CSRF_COOKIE_HTTPONLY settings are enabled.

Setting the token on the AJAX request

Finally, you’ll have to actually set the header on your AJAX request, while protecting the CSRF token from being sent to other domains using settings.crossDomain in jQuery 1.5.1 and newer:

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);
        }
    }
});

If you’re using AngularJS 1.1.3 and newer, it’s sufficient to configure the $http provider with the cookie and header names:

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

Using CSRF in Jinja2 templates

Django’s Jinja2 template backend adds {{ csrf_input }} to the context of all templates which is equivalent to {% csrf_token %} in the Django template language. For example:

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

Подход с декоратором

Вместо добавления CsrfViewMiddleware как общей защиты, вы можете использовать декоратор csrf_protect, который обладает точно такой же функциональностью, для конкретных представлений, которые нужно защитить. Это должно быть использовано и в представлениях, которые вставляют CSRF токен в output, и в той части, что принимает 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)

Если вы использует представления-классы, можете прочитать раздел о декорировании представлений-классов.

Отклоненные запросы

По умолчанию, ответ „403 Forbidden“ отправляется пользователю, если входящий запрос не прошел проверок, выполняемых CsrfViewMiddleware. Обычно это будет происходить, если это действительно CSRF, или из-за ошибки в коде, CSRF токен не был включен в POST форму.

The error page, however, is not very friendly, so you may want to provide your own view for handling this condition. To do this, set the CSRF_FAILURE_VIEW setting.

CSRF failures are logged as warnings to the django.security.csrf logger.

Как это работает

CSRF базируется на следующих вещах:

  1. A CSRF cookie that is based on a random secret value, which other sites will not have access to.

    This cookie is set by CsrfViewMiddleware. It is sent with every response that has called django.middleware.csrf.get_token() (the function used internally to retrieve the CSRF token), if it wasn’t already set on the request.

    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.

    For security reasons, the value of the secret is changed each time a user logs in.

  2. A hidden form field with the name „csrfmiddlewaretoken“ present in all outgoing POST forms. The value of this field is, again, the value of the secret, with a salt which is both added to it and used to scramble it. The salt is regenerated on every call to get_token() so that the form field value is changed in every such response.

    Эта часть выполняет шаблонным тегом.

  3. Все HTTP запросы, которые не GET, HEAD, OPTIONS или TRACE, должны содержать CSRF куку, и поле „csrfmiddlewaretoken“ с правильным значением. Иначе пользователь получит 403 ошибку.

    When validating the „csrfmiddlewaretoken“ field value, only the secret, not the full token, is compared with the secret in the cookie value. This allows the use of ever-changing tokens. While each request may use its own token, the secret remains common to all.

    Эта проверка выполняется в CsrfViewMiddleware.

  4. In addition, for HTTPS requests, strict referer checking is done by CsrfViewMiddleware. This means that even if a subdomain can set or modify cookies on your domain, it can’t force a user to post to your application since that request won’t come from your own exact domain.

    This also addresses a man-in-the-middle attack that’s possible under HTTPS when using a session independent secret, due to the fact that HTTP Set-Cookie headers are (unfortunately) accepted by clients even when they are talking to a site under 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, значение «referer» будет сравниваться с этим значением. Значение поддерживает под-домены. Например, CSRF_COOKIE_DOMAIN = '.example.com' позволить отправлять POST запросы с www.example.com и api.example.com. Если настройка не указана, «referer» должен быть равен HTTP заголовку Host.

    Чтобы расширить список доступных доменов, кроме текущего хоста и домена кук, используйте CSRF_TRUSTED_ORIGINS.

Такой подход гарантирует, что только формы, отправленные с доверенных доменов, могут передавать POST данные.

It deliberately ignores GET requests (and other requests that are defined as „safe“ by RFC 7231#section-4.2.1). These requests ought never to have any potentially dangerous side effects, and so a CSRF attack with a GET request ought to be harmless. RFC 7231#section-4.2.1 defines POST, PUT, and DELETE as „unsafe“, and all other methods are also assumed to be unsafe, for maximum protection.

The CSRF protection cannot protect against man-in-the-middle attacks, so use HTTPS with HTTP Strict Transport Security. It also assumes validation of the HOST header and that there aren’t any cross-site scripting vulnerabilities on your site (because XSS vulnerabilities already let an attacker do anything a CSRF vulnerability allows and much worse).

Removing the Referer header

To avoid disclosing the referrer URL to third-party sites, you might want to disable the referer on your site’s <a> tags. For example, you might use the <meta name="referrer" content="no-referrer"> tag or include the Referrer-Policy: no-referrer header. Due to the CSRF protection’s strict referer checking on HTTPS requests, those techniques cause a CSRF failure on requests with „unsafe“ methods. Instead, use alternatives like <a rel="noreferrer" ...>" for links to third-party sites.

Кеширование

При использовании шаблонного тега csrf_token (или вызове функции get_token) CsrfViewMiddleware добавит куку и заголовок Vary: Cookie в ответ. Это значит, что CsrfViewMiddleware правильно работает с кеширующим промежуточным слоем, если все делать по инструкции (UpdateCacheMiddleware следует перед всеми остальными промежуточными слоями).

Однако, если вы использует кеширующий декоратор для конкретного представления, CSRF промежуточный слой еще не установил «Vary» заголовок или CSRF куку, и ответ будет закеширован без них. В таком случае для всех таких представлений, которые требуют 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):
    ...

Если вы использует представления-классы, можете прочитать раздел о декорировании представлений-классов.

Тестирование

CsrfViewMiddleware – обычно большая помеха при тестировании представлений, т.к. каждый POST запрос должен содержать CSRF токен. По этому тестовый клиент Django для каждого запроса устанавливает специальный флаг, который учитывается декоратором csrf_protect и не возвращает ошибку. Во всем остальном (например, установка кук) запросы работают как обычно.

Если, по какой-то причине, вы хотите, чтобы тестовый клиент выполнял проверку CSRF, вы можете создать специальный экземпляр тестового клиента:

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

Ограничения

Поддомены сайта могут устанавливать куки на клиенте для всего домена. Установив соответствующий токен в куку, поддомены могут обойти CSRF защиту. Единственный способ обезопаситься – быть уверенным, что поддомены контролируются доверенными пользователями (или хотя бы не могут устанавливать куки). Обратите внимание, даже не учитывая CSRF, есть много других уязвимостей, например фиксация сессии(session fixation), от которых нельзя легко обезопаситься с современными браузерами. По этому предоставлять контроль над поддоменом непроверенным людям – плохая идея.

Особые случаи

Некоторые представления могут потребовать особого процесса обработки запроса, который не вписывается в описанный выше шаблон. Ниже приведен ряд функций, которые могут пригодиться в таком случае.

Утилиты

Примеры ниже предполагают, чтобы используете представления-функции. Если вы используете представления-классы, обратитесь к этому разделу.

csrf_exempt(view)

Этот декоратор позволяет исключить представление из процесса проверки CSRF. Например:

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 куку.

Сценарии

Необходимо отключить CSRF защиту для некоторых представлений

Для большинства представлений необходима CSRF защита, но для некоторых необходимо отключить.

Вместо того, чтобы отключить промежуточный слой защиты и добавлять декоратор csrf_protect ко всем представлениям, которым необходима проверка CRSF, включите промежуточный слой и используйте csrf_exempt().

CsrfViewMiddleware.process_view не выполняется

В некоторых случаях CsrfViewMiddleware.process_view может не выполняться перед вашим представлением, например для обработчиков 404 и 500 ответов. Но вам все еще необходим CSRF токен для формы.

Решение: используйте requires_csrf_token()

Для незащищенного представления необходим CSRF токен

Некоторые представления могут быть исключены из процесса проверки CRSF с помощью декоратора csrf_exempt, но им необходим CSRF токен.

Решение: используйте requires_csrf_token() после csrf_exempt(). (То есть requires_csrf_token должен быть самым «внутренним» декоратором).

Представлению необходима проверка CSRF только для определенного пути

Представлению необходима проверка 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 формы

Страница выполняет POST запрос через AJAX, на странице нет HTML формы с тегом csrf_token, который вызывает отправку CSRF куки.

Решение: используйте ensure_csrf_cookie() в представлении, которое отсылает страницу.

Встроенные и распространяемые приложения

Т.к. разработчик может отключить CsrfViewMiddleware, все соответствующие представления во встроенных приложениях используют декоратор csrf_protect, чтобы убедиться, что эти представления защищены от CSRF. Рекомендуется, чтобы разработчики распространяемых приложений также использовали csrf_protect, если представлениям необходима защита от CSRF атак.

Параметры

Параметры, которые могут быть использованы для управления поведением CSRF в Django:

Frequently Asked Questions

Is posting an arbitrary CSRF token pair (cookie and POST data) a vulnerability?

No, this is by design. Without a man-in-the-middle attack, there is no way for an attacker to send a CSRF token cookie to a victim’s browser, so a successful attack would need to obtain the victim’s browser’s cookie via XSS or similar, in which case an attacker usually doesn’t need CSRF attacks.

Some security audit tools flag this as a problem but as mentioned before, an attacker cannot steal a user’s browser’s CSRF cookie. «Stealing» or modifying your own token using Firebug, Chrome dev tools, etc. isn’t a vulnerability.

Is it a problem that Django’s CSRF protection isn’t linked to a session by default?

No, this is by design. Not linking CSRF protection to a session allows using the protection on sites such as a pastebin that allow submissions from anonymous users which don’t have a session.

If you wish to store the CSRF token in the user’s session, use the CSRF_USE_SESSIONS setting.

Why might a user encounter a CSRF validation failure after logging in?

For security reasons, CSRF tokens are rotated each time a user logs in. Any page with a form generated before a login will have an old, invalid CSRF token and need to be reloaded. This might happen if a user uses the back button after a login or if they log in a different browser tab.