Защита от подделки межсайтовых запросов¶
Промежуточное ПО CSRF и тег шаблона обеспечивают простую в использовании защиту от Cross Site Request Forgeries. Этот тип атаки возникает, когда вредоносный сайт содержит ссылку, кнопку формы или какой-либо JavaScript, предназначенный для выполнения некоторого действия на вашем сайте, используя учетные данные вошедшего в систему пользователя, который посещает вредоносный сайт в своем браузере. Также рассматривается смежный тип атаки, „login CSRF“, когда атакующий сайт обманом заставляет браузер пользователя войти на сайт с чужими учетными данными.
Первым средством защиты от CSRF-атак является обеспечение того, чтобы GET-запросы (и другие «безопасные» методы, как определено RFC 7231#section-4.2.1) не имели побочных эффектов. Запросы с помощью «небезопасных» методов, таких как POST, PUT и DELETE, можно защитить, выполнив следующие действия.
Как его использовать¶
Чтобы воспользоваться преимуществами защиты от CSRF в ваших представлениях, выполните следующие шаги:
По умолчанию промежуточное ПО CSRF активировано в настройках
MIDDLEWARE
. Если вы переопределите эту настройку, помните, что'django.middleware.csrf.CsrfViewMiddleware'
должно стоять перед любым промежуточным ПО представления, которое предполагает, что CSRF-атаки были обработаны.Если вы отключили его, что не рекомендуется, вы можете использовать
csrf_protect()
на определенных представлениях, которые вы хотите защитить (см. ниже).В любом шаблоне, использующем форму POST, используйте тег
csrf_token
внутри элемента<form>
, если форма предназначена для внутреннего URL, например:<form method="post">{% csrf_token %}
Этого не следует делать для POST-форм, нацеленных на внешние URL, поскольку в этом случае произойдет утечка CSRF-токена, что приведет к уязвимости.
В соответствующих функциях представления убедитесь, что
RequestContext
используется для рендеринга ответа, чтобы{% csrf_token %}
работал правильно. Если вы используете функциюrender()
, общие представления или приложения contrib, вы уже защищены, поскольку все они используютRequestContext
.
AJAX¶
Хотя описанный выше метод можно использовать для AJAX POST-запросов, он имеет некоторые неудобства: вы должны помнить о необходимости передавать CSRF-токен в качестве POST-данных при каждом POST-запросе. По этой причине существует альтернативный метод: при каждом XMLHttpRequest устанавливайте в пользовательский заголовок X-CSRFToken
(как указано в настройке CSRF_HEADER_NAME
) значение маркера CSRF. Это часто проще, поскольку многие JavaScript-фреймворки предоставляют крючки, которые позволяют устанавливать заголовки при каждом запросе.
Сначала необходимо получить токен CSRF. Как это сделать, зависит от того, включены ли параметры CSRF_USE_SESSIONS
и CSRF_COOKIE_HTTPONLY
.
Получение маркера, если CSRF_USE_SESSIONS
и CSRF_COOKIE_HTTPONLY
составляют False
¶
Рекомендуемым источником маркера является cookie csrftoken
, который будет установлен, если вы включили защиту CSRF для ваших представлений, как описано выше.
По умолчанию маркер CSRF имеет имя csrftoken
, но вы можете управлять именем cookie с помощью параметра CSRF_COOKIE_NAME
.
Приобрести токен очень просто:
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 = cookies[i].trim();
// 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 library для замены getCookie
:
var csrftoken = Cookies.get('csrftoken');
Примечание
CSRF-токен также присутствует в DOM, но только если он явно включен в шаблон с помощью csrf_token
. Cookie содержит канонический токен; CsrfViewMiddleware
предпочтет cookie токену в DOM. Независимо от этого, вы гарантированно получите cookie, если маркер присутствует в DOM, поэтому вы должны использовать cookie!
Предупреждение
Если ваше представление не выводит шаблон, содержащий тег шаблона csrf_token
, Django может не установить куки CSRF-токена. Это часто встречается в случаях, когда формы динамически добавляются на страницу. Для решения этой проблемы Django предоставляет декоратор представления, который принудительно устанавливает cookie: ensure_csrf_cookie()
.
Получение маркера, если CSRF_USE_SESSIONS
или CSRF_COOKIE_HTTPONLY
- True
¶
Если вы активируете CSRF_USE_SESSIONS
или CSRF_COOKIE_HTTPONLY
, вы должны включить маркер CSRF в ваш HTML и прочитать маркер из DOM с помощью JavaScript:
{% csrf_token %}
<script type="text/javascript">
// using jQuery
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
</script>
Установка маркера в запросе AJAX¶
Наконец, вам придется фактически установить заголовок в запросе 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);
}
}
});
Если вы используете AngularJS 1.1.3 и новее, достаточно настроить провайдер $http
с именами cookie и заголовков:
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
Использование CSRF в шаблонах Jinja2¶
Бэкенд шаблонов Django Jinja2
добавляет {{ csrf_input }}
в контекст всех шаблонов, что эквивалентно {% csrf_token %}
в языке шаблонов Django. Например:
<form method="post">{{ csrf_input }}
Метод декоратора¶
Вместо того чтобы добавлять CsrfViewMiddleware
в качестве абсолютной защиты, вы можете использовать декоратор csrf_protect
, который имеет точно такую же функциональность, в определенных представлениях, которые нуждаются в защите. Он должен использоваться ** как** для представлений, которые вставляют CSRF-токен в вывод, так и для тех, которые принимают данные 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)
Если вы используете представления на основе классов, вы можете обратиться к Decorating class-based views.
Отклоненные запросы¶
По умолчанию пользователю отправляется ответ „403 Forbidden“, если входящий запрос не проходит проверку, выполняемую CsrfViewMiddleware
. Обычно это происходит только в случае настоящей подделки межсайтового запроса, или когда из-за ошибки программирования CSRF-токен не был включен в POST-форму.
Однако страница ошибки не очень дружелюбна, поэтому вы можете захотеть создать собственное представление для обработки этого условия. Для этого просто установите параметр CSRF_FAILURE_VIEW
.
Сбои CSRF регистрируются как предупреждения в журнале django.security.csrf.
Как это работает¶
Защита от CSRF основана на следующих моментах:
CSRF-куки, основанные на случайном секретном значении, к которому другие сайты не будут иметь доступа.
Этот файл cookie устанавливается функцией
CsrfViewMiddleware
. Он отправляется с каждым ответом, в котором был вызванdjango.middleware.csrf.get_token()
(функция, используемая для получения маркера CSRF), если он еще не был установлен в запросе.Для защиты от атак BREACH токен - это не просто секрет; к секрету добавляется случайная соль, которая используется для скремблирования.
В целях безопасности значение секрета изменяется каждый раз, когда пользователь входит в систему.
Скрытое поле формы с именем „csrfmiddlewaretoken“, присутствующее во всех исходящих POST-формах. Значение этого поля - это, опять же, значение секрета с солью, которая добавляется к нему и используется для скремблирования. Соль обновляется при каждом вызове
get_token()
, так что значение поля формы изменяется в каждом таком ответе.Эту часть выполняет тег шаблона.
Для всех входящих запросов, не использующих HTTP GET, HEAD, OPTIONS или TRACE, должен присутствовать CSRF cookie, а поле „csrfmiddlewaretoken“ должно быть настоящим и правильным. Если это не так, пользователь получит ошибку 403.
При проверке значения поля „csrfmiddlewaretoken“ только секрет, а не полный токен, сравнивается с секретом в значении cookie. Это позволяет использовать постоянно меняющиеся маркеры. Хотя каждый запрос может использовать свой собственный токен, секрет остается общим для всех.
Эта проверка выполняется с помощью
CsrfViewMiddleware
.Кроме того, для HTTPS-запросов строгая проверка реферера осуществляется по
CsrfViewMiddleware
. Это означает, что даже если поддомен может устанавливать или изменять cookies на вашем домене, он не может заставить пользователя отправить сообщение в ваше приложение, поскольку этот запрос не будет исходить именно от вашего домена.Это также устраняет атаку «человек посередине», которая возможна в HTTPS при использовании независимого от сессии секрета, из-за того, что заголовки HTTP
Set-Cookie
(к сожалению) принимаются клиентами, даже когда они разговаривают с сайтом в HTTPS. (Проверка ссылок не выполняется для HTTP-запросов, потому что наличие заголовкаReferer
не является достаточно надежным в HTTP).Если установлен параметр
CSRF_COOKIE_DOMAIN
, то ссылка сравнивается с ним. Эта настройка поддерживает субдомены. Например,CSRF_COOKIE_DOMAIN = '.example.com'
будет разрешать POST-запросы отwww.example.com
иapi.example.com
. Если параметр не установлен, то реферер должен соответствовать заголовку HTTPHost
.Расширить список принимаемых ссылок за пределы текущего хоста или домена cookie можно с помощью параметра
CSRF_TRUSTED_ORIGINS
.
Это гарантирует, что только формы, полученные из доверенных доменов, могут быть использованы для POST данных.
Он намеренно игнорирует GET-запросы (и другие запросы, которые определены как «безопасные» с помощью RFC 7231#section-4.2.1). Эти запросы не должны иметь никаких потенциально опасных побочных эффектов, поэтому CSRF-атака с помощью GET-запроса должна быть безвредной. RFC 7231#section-4.2.1 определяет POST, PUT и DELETE как «небезопасные», и все остальные методы также считаются небезопасными, для максимальной защиты.
Защита CSRF не может защитить от атак типа «человек посередине», поэтому используйте HTTPS с Строгая транспортная безопасность HTTP. Это также предполагает validation of the HOST header и отсутствие cross-site scripting vulnerabilities на вашем сайте (потому что XSS-уязвимости уже позволяют злоумышленнику делать все, что позволяет CSRF-уязвимость, и даже намного хуже).
Удаление заголовка Referer
Чтобы избежать раскрытия URL-адреса реферера сторонним сайтам, вы можете использовать теги disable the referer на вашем сайте <a>
. Например, вы можете использовать тег <meta name="referrer" content="no-referrer">
или включить заголовок Referrer-Policy: no-referrer
. Из-за того, что защита CSRF строго проверяет ссылки на запросы HTTPS, эти техники вызывают отказ CSRF на запросы с «небезопасными» методами. Вместо этого используйте альтернативы, такие как <a rel="noreferrer" ...>"
для ссылок на сторонние сайты.
Кэширование¶
Если тег шаблона csrf_token
используется шаблоном (или функция get_token
вызывается каким-либо другим способом), CsrfViewMiddleware
добавит cookie и заголовок Vary: Cookie
в ответ. Это означает, что промежуточное ПО будет хорошо взаимодействовать с промежуточным ПО кэша, если оно используется в соответствии с инструкциями (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):
...
Если вы используете представления на основе классов, вы можете обратиться к Decorating class-based views.
Тестирование¶
Декоратор CsrfViewMiddleware
обычно сильно мешает тестированию функций представления из-за необходимости использования CSRF-токена, который должен быть отправлен с каждым POST-запросом. По этой причине HTTP-клиент Django для тестов был модифицирован для установки флага в запросах, который ослабляет промежуточное ПО и декоратор csrf_protect
так, что они больше не отклоняют запросы. Во всех остальных отношениях (например, отправка cookies и т.д.) они ведут себя одинаково.
Если по какой-то причине вы хотите, чтобы тестовый клиент выполнял проверку CSRF, вы можете создать экземпляр тестового клиента, который будет выполнять проверку CSRF:
>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)
Ограничения¶
Поддомены в рамках сайта смогут устанавливать cookie на клиенте для всего домена. Устанавливая куки и используя соответствующий токен, поддомены смогут обойти защиту CSRF. Единственный способ избежать этого - убедиться, что поддомены контролируются доверенными пользователями (или, по крайней мере, не могут устанавливать cookies). Обратите внимание, что даже без CSRF существуют другие уязвимости, такие как фиксация сессии, которые делают передачу поддоменов недоверенным лицам плохой идеей, и эти уязвимости не могут быть легко устранены в современных браузерах.
Краевые случаи¶
Определенные представления могут иметь необычные требования, которые означают, что они не вписываются в обычную схему, предусмотренную здесь. В таких ситуациях может быть полезен ряд утилит. Сценарии, в которых они могут понадобиться, описаны в следующем разделе.
Утилиты¶
Приведенные ниже примеры предполагают, что вы используете представления на основе функций. Если вы работаете с представлениями на основе классов, вы можете обратиться к Decorating class-based views.
-
csrf_exempt
(view)[исходный код]¶ Этот декоратор помечает представление как освобожденное от защиты, обеспечиваемой промежуточным ПО. Пример:
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)
-
ensure_csrf_cookie
(view)¶ Этот декоратор заставляет представление отправлять CSRF cookie.
Сценарии¶
Защита CSRF должна быть отключена только для нескольких представлений¶
Большинство представлений требуют защиты от CSRF, но некоторые не требуют.
Решение: вместо того чтобы отключать промежуточное ПО и применять csrf_protect
ко всем представлениям, которым оно необходимо, включите промежуточное ПО и используйте csrf_exempt()
.
CsrfViewMiddleware.process_view не используется¶
Бывают случаи, когда CsrfViewMiddleware.process_view
может не выполняться до запуска вашего представления - например, обработчики 404 и 500 - но вам все равно нужен CSRF-токен в форме.
Решение: используйте requires_csrf_token()
Незащищенное представление нуждается в маркере CSRF¶
Могут быть некоторые представления, которые не защищены и были исключены с помощью csrf_exempt
, но все равно должны включать маркер CSRF.
Решение: используйте 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-формы¶
Страница делает POST-запрос через AJAX, и на странице нет HTML-формы с csrf_token
, которая бы вызвала отправку требуемого CSRF-куки.
Решение: используйте ensure_csrf_cookie()
в представлении, которое отправляет страницу.
Contrib и многократно используемые приложения¶
Поскольку разработчик может отключить CsrfViewMiddleware
, все соответствующие представления в приложениях contrib используют декоратор csrf_protect
для обеспечения безопасности этих приложений от CSRF. Рекомендуется, чтобы разработчики других многократно используемых приложений, которые хотят получить такие же гарантии, также использовали декоратор csrf_protect
в своих представлениях.
Настройки¶
Для управления поведением Django в отношении CSRF можно использовать ряд настроек:
Часто задаваемые вопросы¶
Является ли размещение произвольной пары CSRF-токенов (cookie и POST-данные) уязвимостью?¶
Нет, это сделано специально. Без атаки «человек посередине» у злоумышленника нет возможности отправить CSRF маркер cookie в браузер жертвы, поэтому для успешной атаки необходимо получить cookie браузера жертвы через XSS или подобным образом, в этом случае злоумышленнику обычно не нужны CSRF атаки.
Некоторые инструменты аудита безопасности отмечают это как проблему, но, как уже говорилось, злоумышленник не может украсть CSRF-куки браузера пользователя. «Кража» или изменение собственного маркера с помощью Firebug, инструментов разработчика Chrome и т.д. не является уязвимостью.
Является ли проблемой то, что защита CSRF в Django по умолчанию не связана с сессией?¶
Нет, это сделано специально. Отсутствие привязки защиты CSRF к сессии позволяет использовать защиту на таких сайтах, как pastebin, которые позволяют отправлять сообщения от анонимных пользователей, не имеющих сессии.
Если вы хотите хранить CSRF-токен в сессии пользователя, используйте параметр CSRF_USE_SESSIONS
.
Почему пользователь может столкнуться с ошибкой проверки CSRF после входа в систему?¶
В целях безопасности маркеры CSRF меняются каждый раз, когда пользователь входит в систему. Любая страница с формой, созданной до входа в систему, будет иметь старый, недействительный CSRF-токен, и ее необходимо будет перезагрузить. Это может произойти, если пользователь использует кнопку «назад» после входа в систему или если он входит в другую вкладку браузера.