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

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

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

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

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

  1. Добавьте промежуточный слой 'django.middleware.csrf.CsrfViewMiddleware' в ваш список классов промежуточных слоев, MIDDLEWARE_CLASSES. (Это должно быть до первого из промежуточных слоев представлений, что даст возможность предотвратить CSRF атаки)

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

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

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

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

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

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

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

      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)
      

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

Вспомогательный скрипт extras/csrf_migration_helper.py (расположен в поставке Django, но не проинсталирован ) может помочь автоматизировать поиск кода и шаблонов, которые могут потребовать выполнения этих шагов по обеспечению безопасности. Скрипт содержит полное описание по своему использованию.

AJAX

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

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

Примечание

Кука CSRF токена называется csrftoken по умолчанию, но вы можете управлять именем куки при помощи параметра CSRF_COOKIE_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');

Вышеприведенный код может быть упрощен при помощи плагина jQuery для замены getCookie:

var csrftoken = $.cookie('csrftoken');

Примечание

CSRF токен также представлен в DOM, но только если он явно включен, используя csrf_token в шаблоне. Кука содержит канонический токен; CsrfViewMiddleware будет предпочитать этот куки куке из DOM. Не смотря на это, вы гарантировано имеете куки, если токен представлен в DOM, так что вы должны использовать куки.

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

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

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

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
function sameOrigin(url) {
    // test that a given url is a same-origin 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));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
            // Send the token to same-origin, relative URLs only.
            // Send the token only if the method warrants CSRF protection
            // Using the CSRFToken value acquired earlier
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

Примечание

Из-за ошибки, допущенной в jQuery 1.5, пример выше не будет работать правильно в этой версии. Убедитесь, что вы используете по крайней мере jQuery 1.5.1.

Вы можете использовать settings.crossDomain в jQuery 1.5 и новее для того чтобы заменить логику sameOrigin, описанную выше:

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

Примечание

В security release blogpost был предоставлен простой “same origin test” пример, который проверяет только относительный URL. sameOrigin тест выше заменяет этот пример - это работает для крайних случаев подобных зависимым от схем или абсолютным URL’ам для того же домена.

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

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

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

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

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

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

Кеширование

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