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

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

Первая защита против CSRF атак - это гарантирование того, что GET запросы (и другие ‘безопасные’ методы, определенные в 9.1.1 Safe Methods, HTTP 1.1, RFC 2616#section-9.1.1) свободны от побочных эффектов. Запросы через ‘небезопасные’ методы, такие как POST, PUT и DELETE могут быть защищены при помощи шагов, описанных ниже.

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

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

  1. Промежуточный слой CSRF активирован по умолчанию и находится в настройке MIDDLEWARE_CLASSES. Если вы переопределяете эту настройку, помните, что 'django.middleware.csrf.CsrfViewMiddleware' должен следовать перед промежуточными слоями, которые предполагают, что запрос уже проверен на CSRF атаку.

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

  2. В любом шаблоне, который использует POST форму, используйте тег csrf_token внутри элемента <form> если форма для внутреннего URL, т. е.:

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

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

  3. В соответствующих функциях представления, убедитесь, что 'django.template.context_processors.csrf' контекстный процессор используется. Обычно, это может быть сделано в один из двух способов:

    1. Использовать RequestContext, который всегда использует 'django.template.context_processors.csrf' (не зависимо от параметра TEMPLATES ). Если вы используете общие представления или contrib приложения, вы уже застрахованы, так как эти приложения используют RequestContext повсюду.

    2. Вручную импортировать и использовать процессор для генерации CSRF токена и добавить в шаблон контекста. т.е.:

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

      Вы можете захотеть написать собственный render_to_response() враппер, который позаботится об этом шаге для вас.

AJAX

Хотя вышеописанный метод может быть использован для AJAX POST запросов, он имеет некоторые неудобства: вы должны не забывать передавать CSRF токен в POST данных с каждым POST запросом. По этой причине есть альтернативный метод: для каждого XMLHttpRequest можно устанавливать кастомный заголовок X-CSRFToken в значение CSRF токена. Это проще, потому что многие JavaScript фреймворки предоставляют хуки, которые позволяют устанавливать заголовки для каждого запроса.

В качестве первого шага, вы должны получить CSRF токен самостоятельно. Рекомендованный источник для токен - это кука csrftoken , которая будет установлена, если вы включили CSRF защиту для ваших представлений как описано выше.

Примечание

Кука CSRF токена называется csrftoken по умолчанию, но вы можете управлять именем куки при помощи параметра CSRF_COOKIE_NAME.

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 в шаблоне. Кука содержит канонический токен; CsrfViewMiddleware будет предпочитать этот куки куке из DOM. Не смотря на это, вы гарантировано имеете куки, если токен представлен в DOM, так что вы должны использовать куки.

Предупреждение

Если ваше представление не рендерит шаблон, содержащий шаблонный тег csrf_token, Django может не установить куки с CSRF токеном.

В конце концов, вы будете должны фактически установить заголовок в вашем 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);
        }
    }
});

Другие шаблонизаторы

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

Например, в шаблонизаторе Jinja2 , ваша форма может содержать следующее:

<div style="display:none">
    <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
</div>

Вы можете использовать JavaScript подобный AJAX код для получения значения CSRF токена.

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

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

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

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

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

Страница ошибки, не очень дружелюбна, поэтому вы можете предоставлять собственное представление для обработки этого состояния. Чтобы сделать это, просто установите параметр CSRF_FAILURE_VIEW.

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

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

  1. CSRF кука, которая устанавливается как случайное число (сессия независимого случайного слова, как это еще иногда называют), к которой другие сайты не будут иметь доступа.

    Эта кука устанавливается при помощи CsrfViewMiddleware. Она должно быть постоянной, но так как нет способа установить куки, у которых никогда не истекает время жизни, то она отправляется с каждым ответом, который вызывал django.middleware.csrf.get_token() (функция использовалась внутри для получения CSRF токена).

  2. Все POST формы содержат скрытое поле ‘csrfmiddlewaretoken’. Значение поля равно CSRF куке.

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

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

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

  4. В дополнение для HTTPS запросов в CsrfViewMiddleware проверяется “referer”(источник запроса). Это необходимо для предотвращения MITM-атаки(Man-In-The-Middle), которая возможна при использовании HTTPS и токена не привязанного к сессии, т.к. клиенты принимают(к сожалению) HTTP заголовок ‘Set-Cookie’, несмотря на то, что коммуникация с сервером происходит через HTTPS. (Такая проверка не выполняется для HTTP запросов т.к. “Referer” заголовок легко подменить при использовании HTTP.)

    Если указана настройка CSRF_COOKIE_DOMAIN, значение “referer” будет сравниваться с этим значением. Значение поддерживает под-домены. Например, CSRF_COOKIE_DOMAIN = '.example.com' позволить отправлять POST запросы с www.example.com и api.example.com. Если настройка не указана, “referer” должен быть равен HTTP заголовку Host.

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

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

GET игнорируются сознательно (и все другие запросы, которые считаются “безопасными” в соответствии с RFC 2616). Эти запросы никогда не должны выполнять каких-либо потенциально опасные действия, и CSRF атаки через GET запрос должен быть безвредным. RFC 2616 определяет POST, PUT и DELETE как “небезопасные”.

Изменено в Django 1.9:

Была добавлена проверка настройки CSRF_COOKIE_DOMAIN.

Кеширование

При использовании шаблонного тега 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.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)

Обычно шаблонный тег csrf_token ничего не делает, если CsrfViewMiddleware.process_view, или его аналог csrf_protect, не был выполнен. Декоратор 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 куку.

Сценарии

Необходимо отключить 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: