Защита от межсайтовых поддельных запросов
Средство промежуточного ПО 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. Способ получения зависит от того, включена ли настройка CSRF_USE_SESSIONS.
Получение токена, если CSRF_USE_SESSIONS равно False
Рекомендуемым источником токена является 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 для замены 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().
Получение токена, если CSRF_USE_SESSIONS равно True
Если вы активируете CSRF_USE_SESSIONS, вы должны включить токен 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’s Jinja2 шаблонный бэкэнд добавляет {{ csrf_input }} в контекст всех шаблонов, что эквивалентно {% csrf_token %} на языке шаблонов Django. Например:
<form action="" method="post">{{ csrf_input }}
Метод декоратора
Вместо добавления CsrfViewMiddleware в качестве общей защиты, вы можете использовать декоратор csrf_protect, который имеет точно такую же функциональность, для конкретных представлений, которым нужна защита. Он должен использоваться как для представлений, вставляющих токен CSRF в вывод, так и для представлений, принимающих данные формы 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)Если вы используете представления на основе классов, вы можете обратиться к Декорирование представлений на основе классов.
Отклоненные запросы
По умолчанию, если входящий запрос не пройдёт проверки, выполняемые CsrfViewMiddleware, пользователю отправляется ответ «403 Запрещено». Это обычно наблюдается только в случае реальной атаки межсайтовых поддельных запросов или если из-за ошибки программирования токен CSRF не был включен в форму POST.
Однако страница ошибки не очень удобная, поэтому вы можете предоставить собственное представление для обработки этого условия. Для этого просто установите настройку CSRF_FAILURE_VIEW.
Ошибки CSRF регистрируются как предупреждения в логере django.security.csrf.
В более старых версиях ошибки CSRF регистрируются в логере django.request.
Как это работает
Защита от 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 на вашем домене, он не может заставить пользователя отправить POST-запрос в ваше приложение, поскольку этот запрос не будет исходить с вашего точного домена.Это также решает проблему атаки «человек посередине» возможную при использовании 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.
Это гарантирует, что только формы, которые исходят из доверенных доменов, могут использоваться для отправки данных обратно.
Она намеренно игнорирует запросы GET (и другие запросы, определённые как «безопасные» в RFC 7231). Эти запросы никогда не должны иметь потенциально опасных побочных эффектов, и поэтому атака CSRF с запросом GET должна быть безопасной. RFC 7231 определяет POST, PUT и DELETE как «опасные», а все остальные методы также предполагаются небезопасными для максимальной защиты.
Защита CSRF не может защитить от атак «человек посередине», поэтому используйте HTTPS с HTTP Strict Transport Security. Она также предполагает валидацию заголовка HOST и отсутствие уязвимостей межсайтового скриптинга на вашем сайте (потому что уязвимости XSS позволяют злоумышленнику сделать всё, что позволяет уязвимость CSRF, и гораздо больше).
Добавлена соль в маркер и начато его изменение с каждым запросом для защиты от атак 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)
Ограничения CSRF
Субдомены на сайте смогут устанавливать 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, что привело бы к отправке необходимого куки 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_ORIGINSCSRF_USE_SESSIONS
Часто задаваемые вопросы
Является ли отправка произвольной пары токенов CSRF (куки и данные POST) уязвимостью?
Нет, это сделано по умолчанию. Без атаки «человек посередине» нет способа для злоумышленника отправить куки токена CSRF в браузер жертвы, поэтому успешная атака должна получить куки браузера жертвы через XSS или аналогичным способом, в этом случае злоумышленнику обычно не нужны атаки CSRF.
Некоторые инструменты аудита безопасности отмечают это как проблему, но, как упоминалось ранее, злоумышленник не может украсть куки CSRF браузера пользователя. «Кража» или изменение своего собственного токена с помощью Firebug, инструментов разработчика Chrome и т.д. не является уязвимостью.
Это проблема, что защита Django CSRF по умолчанию не связана с сессией?
Нет, это сделано по умолчанию. Отсутствие связи защиты CSRF с сессией позволяет использовать защиту на сайтах, таких как pastebin, которые позволяют отправлять запросы от анонимных пользователей, у которых нет сессии.
Если вы хотите сохранить токен CSRF в сессии пользователя, используйте настройку CSRF_USE_SESSIONS.
Почему пользователь может столкнуться с ошибкой проверки CSRF после входа в систему?
По соображениям безопасности токены CSRF обновляются каждый раз, когда пользователь входит в систему. Любая страница с формой, сгенерированной до входа в систему, будет иметь старый, недействительный токен CSRF и потребует перезагрузки. Это может произойти, если пользователь использует кнопку «Назад» после входа или если он вошел в систему в другой вкладке браузера.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/ref/csrf/