Защита от межсайтовых поддельных запросов
Модуль CSRF-средства промежуточного ПО и тег шаблона предоставляют простую в использовании защиту от межсайтовых поддельных запросов (CSRF). Этот тип атаки происходит, когда вредоносный веб-сайт содержит ссылку, кнопку формы или какой-либо JavaScript-код, предназначенный для выполнения некоторого действия на вашем веб-сайте с использованием учетных данных вошедшего пользователя, который посещает вредоносный сайт в своём браузере. Также обрабатывается связанный тип атаки — «CSRF входа», при котором атакующий сайт обманывает браузер пользователя, чтобы тот вошёл на сайт под чьими-то чужими учетными данными.
Первая защита от атак CSRF заключается в обеспечении того, чтобы запросы GET (и другие «безопасные» методы, как определено в RFC 7231#section-4.2.1) не имели побочных эффектов. Запросы с помощью «небезопасных» методов, таких как POST, PUT и DELETE, можно защитить, выполнив следующие шаги.
Как его использовать
Чтобы воспользоваться защитой CSRF в ваших представлениях, выполните следующие шаги:
-
Средства промежуточного ПО CSRF по умолчанию активированы в настройке
MIDDLEWARE_CLASSES. Если вы переопределите эту настройку, помните, что'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 для замены 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. (Это часто одна и та же функция представления, но не всегда).
Использование только декоратора не рекомендуется, так как при его забытии вы создадите брешь в безопасности. Стратегия «обеспечения безопасности с двух сторон» — использование обоих — подходит и потребует минимальных накладных расходов.
-
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, который устанавливается со случайным значением (независимый от сессии nonce), к которому у других сайтов нет доступа.
Этот cookie устанавливается
CsrfViewMiddleware. Он предназначен для постоянного использования, но так как нет способа установить cookie, который никогда не истечёт, он отправляется с каждым ответом, вызвавшимdjango.middleware.csrf.get_token()(функция, используемая внутри для извлечения токена CSRF).По соображениям безопасности значение cookie CSRF изменяется каждый раз, когда пользователь входит в систему.
-
Скрытое поле формы с именем «csrfmiddlewaretoken», присутствующее во всех исходящих формах POST. Значение этого поля — значение cookie CSRF.
Эта часть выполняется тегом шаблона.
-
Для всех входящих запросов, которые не используют HTTP GET, HEAD, OPTIONS или TRACE, должен присутствовать cookie CSRF, и поле «csrfmiddlewaretoken» должно присутствовать и быть корректным. Если это не так, пользователю будет отправлена ошибка 403.
Эта проверка выполняется
CsrfViewMiddleware. -
Кроме того, для запросов HTTPS выполняется строгая проверка referer
CsrfViewMiddleware. Это означает, что даже если дочерний домен может устанавливать или изменять cookie на вашем домене, он не может заставить пользователя выполнить POST-запрос на ваше приложение, так как этот запрос не будет исходить с вашего точного домена.Это также устраняет возможность атаки «человек посередине», которая возможна при использовании независимого от сессии nonce по протоколу 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.
Кэширование
Если тег шаблона csrf_token используется в шаблоне (или функция get_token вызывается другим способом), CsrfViewMiddleware добавит cookie и заголовок Vary: Cookie в ответ. Это означает, что middleware будет корректно работать с middleware кэширования, если оно используется как предписано (UpdateCacheMiddleware должен стоять перед всеми остальными middleware).
Однако, если вы используете декораторы кэша для отдельных представлений, middleware CSRF ещё не сможет установить заголовок 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 для своих представлений.
Настройки
Для управления поведением Django CSRF можно использовать ряд настроек:
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 и т. д. не является уязвимостью.
Является ли тот факт, что защита CSRF Django не связана с сессией, проблемой?
Нет, это сделано по умолчанию. Отсутствие связи защиты CSRF с сессией позволяет использовать защиту на сайтах, таких как pastebin, которые позволяют отправлять данные от анонимных пользователей, у которых нет сессии.
Почему не использовать новый токен для каждого запроса?
Генерация нового токена для каждого запроса проблематична с точки зрения пользовательского интерфейса, поскольку это делает все предыдущие формы недействительными. Большинство пользователей будут очень недовольны, если обнаружат, что открытие новой вкладки на вашем сайте сделало недействительной форму, которую они только что заполнили в другой вкладке, или что форму, к которой они перешли через кнопку «Назад», нельзя заполнить.
Почему пользователь может столкнуться с ошибкой валидации CSRF после входа в систему?
По соображениям безопасности токены CSRF меняются каждый раз, когда пользователь входит в систему. Любая страница с формой, сгенерированной до входа в систему, будет иметь старый, недействительный токен CSRF и потребуется перезагрузка. Это может произойти, если пользователь использует кнопку «Назад» после входа в систему или если он входит в систему в другой вкладке браузера.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/ref/csrf/