Защита от межсайтовых поддельных запросов
Модуль промежуточного ПО CSRF и тег шаблона обеспечивают простую в использовании защиту от межсайтовых поддельных запросов. Этот тип атаки происходит, когда вредоносный веб-сайт содержит ссылку, кнопку формы или JavaScript-код, предназначенные для выполнения определенного действия на вашем веб-сайте с использованием учетных данных вошедшего в систему пользователя, который посещает вредоносный сайт в браузере. Также обрабатывается связанный тип атаки, «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 action="" method="post">{% csrf_token %}Это не следует делать для форм POST, которые обращаются к внешним URL-адресам, так как это приведет к утечке токена CSRF, что приведет к уязвимости.
- В соответствующих функциях представлений убедитесь, что используется
RequestContextдля рендеринга ответа, чтобы{% csrf_token %}работало правильно. Если вы используете функциюrender(), универсальные представления или приложения contrib, вы уже защищены, поскольку все они используютRequestContext.
AJAX
Хотя описанный выше метод можно использовать для AJAX-запросов POST, он имеет некоторые неудобства: вам нужно помнить, чтобы передавать токен CSRF в качестве данных POST с каждым запросом POST. По этой причине существует альтернативный метод: в каждом XMLHttpRequest установите пользовательский заголовок X-CSRFToken со значением токена CSRF. Это часто проще, потому что многие JavaScript-фреймворки предоставляют крючки, которые позволяют устанавливать заголовки в каждом запросе.
В качестве первого шага вам необходимо получить сам токен CSRF. Рекомендуемым источником токена является cookie csrftoken, который будет установлен, если вы включили защиту CSRF для своих представлений, как описано выше.
Примечание
Cookie токена CSRF по умолчанию называется csrftoken, но вы можете управлять именем cookie через настройку 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 библиотеку JavaScript Cookie library для замены getCookie:
var csrftoken = Cookies.get('csrftoken');
Примечание
Токен CSRF также присутствует в DOM, но только если он явно включен с помощью csrf_token в шаблоне. Cookie содержит канонический токен; CsrfViewMiddleware отдаст предпочтение cookie перед токеном в DOM. В любом случае, cookie гарантированно присутствует, если токен присутствует в DOM, поэтому следует использовать cookie!
Предупреждение
Если ваше представление не рендерит шаблон, содержащий тег шаблона csrf_token, Django может не установить cookie токена CSRF. Это часто происходит в случаях, когда формы динамически добавляются на страницу. Для решения этой проблемы Django предоставляет декоратор представления, который принудительно устанавливает cookie: ensure_csrf_cookie().
Наконец, вам нужно будет фактически установить заголовок в вашем 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’s Jinja2 шаблонный движок добавляет {{ csrf_input }} в контекст всех шаблонов, что эквивалентно {% csrf_token %} на языке шаблонов Django. Например:
<form action="" method="post">{{ csrf_input }}
Метод декоратора
Вместо добавления CsrfViewMiddleware в качестве всеобщей защиты, вы можете использовать декоратор csrf_protect, который имеет точно такую же функциональность, для отдельных представлений, которые нуждаются в защите. Он должен использоваться как для представлений, которые вставляют токен CSRF в вывод, так и для тех, которые принимают данные формы POST. (Часто это одна и та же функция представления, но не всегда).
Использование только декоратора не рекомендуется, так как если вы забудете его использовать, у вас будет брешь в безопасности. Стратегия «belt and braces» с использованием обоих способов приемлема и повлечет минимальные накладные расходы.
-
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 Запрещено», если входящий запрос не проходит проверки, выполняемые CsrfViewMiddleware. Это обычно наблюдается только при реальной межсайтовой поддельной атаке или когда из-за ошибки программирования токен CSRF не был включен в форму POST.
Однако страница ошибки не очень удобна, поэтому вы можете предоставить свое собственное представление для обработки этого условия. Для этого просто установите настройку CSRF_FAILURE_VIEW.
Ошибки CSRF регистрируются как предупреждения в логере django.request.
Как работает CSRF
Защита CSRF основана на следующем:
-
Cookie CSRF, основанная на случайном секретном значении, к которому другие сайты не имеют доступа.
Этот cookie устанавливается
CsrfViewMiddleware. Он отправляется с каждым ответом, который вызвалdjango.middleware.csrf.get_token()(функция, используемая для получения токена CSRF), если он не был установлен в запросе.Для защиты от атак BREACH токен не просто секрет; ему добавляется случайная соль для его шифрования.
По соображениям безопасности значение секрета меняется каждый раз, когда пользователь входит в систему.
-
Скрытое поле формы с именем «csrfmiddlewaretoken», присутствующее во всех исходящих формах POST. Значение этого поля — снова значение секрета с солью, которая добавляется к нему и используется для его шифрования. Соль генерируется заново при каждом вызове
get_token()для изменения значения поля формы в каждом таком ответе.Эта часть выполняется тегом шаблона.
-
Для всех входящих запросов, которые не используют HTTP GET, HEAD, OPTIONS или TRACE, должен присутствовать cookie CSRF, а поле «csrfmiddlewaretoken» должно быть присутствовать и правильным. Если это не так, пользователю будет показана ошибка 403.
При проверке значения поля «csrfmiddlewaretoken» сравнивается только секрет, а не весь токен, с секретом в значении cookie. Это позволяет использовать постоянно меняющиеся токены. Хотя каждый запрос может использовать свой собственный токен, секрет остается общим для всех.
Эта проверка выполняется
CsrfViewMiddleware. -
Кроме того, для запросов HTTPS выполняется строгая проверка referer
CsrfViewMiddleware. Это означает, что даже если домен-поддомен может устанавливать или изменять cookie на вашем домене, он не может заставить пользователя отправлять данные на ваше приложение, так как этот запрос не будет исходить из вашего точного домена.Это также устраняет возможность атаки «человек посередине» при использовании независимого от сеанса секрета при использовании HTTPS, поскольку (к сожалению) браузеры принимают заголовки HTTP
Set-Cookie, даже когда они взаимодействуют со сайтом по HTTPS.(Проверка referer не выполняется для запросов HTTP, поскольку наличие заголовка
Refererнедостаточно надежно в HTTP.)Если настроена настройка
CSRF_COOKIE_DOMAIN, то referer сравнивается с ней. Эта настройка поддерживает поддомены. Например,CSRF_COOKIE_DOMAIN = '.example.com'позволит запросам POST изwww.example.comиapi.example.com. Если настройка не установлена, то referer должен соответствовать заголовку HTTPHost.Расширение допустимых referer за пределы текущего хоста или домена cookie можно сделать с помощью настройки
CSRF_TRUSTED_ORIGINS.
Это гарантирует, что только формы, которые имеют происхождение от доверенных доменов, могут использоваться для отправки данных POST обратно.
Он преднамеренно игнорирует запросы GET (и другие запросы, определённые как «безопасные» в RFC 7231). Эти запросы никогда не должны иметь каких-либо потенциально опасных побочных эффектов, поэтому атака CSRF с запросом GET должна быть безвредной. RFC 7231 определяет POST, PUT и DELETE как «опасные», а все остальные методы также предполагаются небезопасными, для максимальной защиты.
Защита от CSRF не может защитить от атак «человек посередине», поэтому используйте HTTPS вместе с HTTP Strict Transport Security. Она также предполагает валидацию заголовка HOST и отсутствие уязвимостей XSS на вашем сайте (поскольку уязвимости XSS уже позволяют злоумышленнику делать всё, что позволяет уязвимость CSRF, и гораздо больше).
Была добавлена проверка по настройке CSRF_COOKIE_DOMAIN.
Добавлено засоление для токена и начато его изменение с каждым запросом для защиты от атак BREACH.
Кэширование
Если тег шаблона csrf_token используется шаблоном (или вызывается функция get_token, другим способом), CsrfViewMiddleware добавит cookie и заголовок Vary: Cookie в ответ. Это означает, что middleware будет хорошо работать с middleware кэширования, если он используется как указано (UpdateCacheMiddleware идёт перед всеми остальными middleware).
Однако, если вы используете декораторы кэширования для отдельных представлений, CSRF middleware ещё не сможет установить заголовок Vary или cookie 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 обычно будет значительным препятствием для тестирования функций представлений из-за необходимости токена CSRF, который необходимо отправлять с каждым запросом POST. По этой причине HTTP-клиент Django для тестов был изменён, чтобы установить флаг в запросах, который ослабляет middleware и декоратор csrf_protect, чтобы они больше не отклоняли запросы. Во всех других отношениях (например, отправка cookie и т. д.) они ведут себя одинаково.
Если по какой-либо причине вы хотите, чтобы клиент теста выполнял проверки CSRF, вы можете создать экземпляр клиента теста, который применяет проверки CSRF:
>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
Ограничения
Поддомены в пределах сайта смогут устанавливать cookie на клиенте для всего домена. Установив cookie и используя соответствующий токен, поддомены смогут обойти защиту CSRF. Единственный способ избежать этого — убедиться, что поддомены контролируются доверенными пользователями (или, по крайней мере, не могут устанавливать cookie). Обратите внимание, что даже без CSRF существуют другие уязвимости, такие как фиксация сеанса, которые делают предоставление поддоменов недоверенным сторонам плохой идеей, и эти уязвимости трудно исправить с помощью современных браузеров.
Крайние случаи
У некоторых представлений могут быть необычные требования, из-за которых они не подходят к обычной схеме, описанной здесь. В таких ситуациях могут быть полезны ряд утилит. Сценарии, в которых они могут потребоваться, описаны в следующем разделе.
Утилиты
Примеры ниже предполагают использование представлений на основе функций. Если вы работаете с представлениями на основе классов, вы можете обратиться к Декорирование представлений на основе классов.
-
csrf_exempt(view)[source] -
Этот декоратор отмечает представление как освобожденное от защиты, обеспечиваемой middleware. Пример:
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)
-
Этот декоратор принудительно заставляет представление отправлять cookie CSRF.
Сценарии
Защита от CSRF должна быть отключена только для нескольких представлений
Большинство представлений требует защиты от CSRF, но несколько нет.
Решение: вместо отключения middleware и применения csrf_protect ко всем представлениям, которые её нуждаются, включите middleware и используйте 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, которая бы вызвала отправку требуемого cookie CSRF.
Решение: используйте ensure_csrf_cookie() в представлении, которое отправляет страницу.
Модули contrib и reusable приложения
Поскольку разработчик может отключить CsrfViewMiddleware, все соответствующие представления в приложениях contrib используют декоратор csrf_protect, чтобы обеспечить безопасность этих приложений от CSRF. Рекомендовано, чтобы разработчики других reusable приложений, которые хотят получить такие же гарантии, также использовали декоратор csrf_protect для своих представлений.
Настройки
Ряд настроек может использоваться для управления поведением CSRF Django:
CSRF_COOKIE_AGECSRF_COOKIE_DOMAINCSRF_COOKIE_HTTPONLYCSRF_COOKIE_NAMECSRF_COOKIE_PATHCSRF_COOKIE_SECURECSRF_FAILURE_VIEWCSRF_HEADER_NAMECSRF_TRUSTED_ORIGINS
Часто задаваемые вопросы
Является ли отправка произвольной пары токен CSRF (cookie и данные POST) уязвимостью?
Нет, это сделано по умолчанию. Без атаки «человек посередине» злоумышленник не может отправить cookie токена CSRF в браузер жертвы, поэтому успешная атака должна получить cookie браузера жертвы через XSS или подобное, в этом случае злоумышленнику обычно не нужны атаки CSRF.
Некоторые инструменты аудита безопасности отмечают это как проблему, но, как уже упоминалось, злоумышленник не может украсть cookie CSRF браузера пользователя. «Кража» или изменение собственного токена с помощью Firebug, инструментов разработчика Chrome и т. д. не является уязвимостью.
Является ли тот факт, что защита Django от CSRF не связана с сеансом, проблемой?
Нет, это сделано по умолчанию. Отсутствие связи защиты CSRF с сессией позволяет использовать защиту на сайтах, таких как pastebin, которые допускают отправку данных от анонимных пользователей, у которых нет сессии.
Почему пользователь может столкнуться с ошибкой проверки CSRF после входа в систему?
По соображениям безопасности токены CSRF обновляются каждый раз, когда пользователь входит в систему. Любая страница с формой, сгенерированной до входа в систему, будет иметь устаревший, недействительный токен CSRF и потребует перезагрузки. Это может произойти, если пользователь использует кнопку "Назад" после входа в систему или если он входит в систему в другой вкладке браузера.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/ref/csrf/