Защита от межсайтовых поддельных запросов
Модуль 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 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 маркера CSRF по умолчанию имеет имя csrftoken , но вы можете управлять именем cookie через настройку CSRF_COOKIE_NAME.
Вы можете получить маркер следующим образом:
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const 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;
}
const csrftoken = getCookie('csrftoken');
Вышеприведенный код можно упростить, используя библиотеку JavaScript Cookie для замены getCookie:
const 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 или CSRF_COOKIE_HTTPONLY True
{% csrf_token %}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>
Установка маркера в AJAX-запросе
Наконец, вам нужно будет установить заголовок в вашем AJAX-запросе. Используя API fetch():
const request = new Request(
/* URL */,
{headers: {'X-CSRFToken': csrftoken}}
);
fetch(request, {
method: 'POST',
mode: 'same-origin' // Do not send CSRF token to another domain.
}).then(function(response) {
// ...
});
Использование CSRF в шаблонах Jinja2
Django's 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)Если вы используете представления на основе классов, см. Декорирование представлений на основе классов.
Отклоненные запросы
По умолчанию пользователю отправляется ответ «403 Запрещено», если входящий запрос не проходит проверки, выполняемые CsrfViewMiddleware. Это обычно наблюдается только при реальной межсайтовой поддельной атаке или если из-за ошибки программирования маркер CSRF не был включен в форму POST.
Однако страница с ошибкой не очень удобна, поэтому вы можете предоставить собственное представление для обработки этого условия. Для этого установите настройку CSRF_FAILURE_VIEW.
Ошибки CSRF записываются в журнал как предупреждения в django.security.csrf логгере.
Как это работает
Защита от CSRF основана на следующих вещах:
-
Куки CSRF, основанный на случайном секретном значении, к которому у других сайтов не будет доступа.
Этот куки устанавливается
CsrfViewMiddleware. Он отправляется с каждым ответом, вызвавшимdjango.middleware.csrf.get_token()(функция, используемая для получения токена CSRF), если он не был уже установлен в запросе.Для защиты от атак BREACH токен не просто содержит секрет; случайная маска добавляется к секрету и используется для его искажения.
По соображениям безопасности значение секрета изменяется каждый раз при входе пользователя в систему.
-
Скрытое поле формы с именем ‘csrfmiddlewaretoken’, присутствующее во всех исходящих формах POST. Значение этого поля, снова же, представляет собой значение секрета с маской, которая добавляется к нему и используется для его искажения. Маска генерируется заново при каждом вызове
get_token(), чтобы значение поля формы менялось в каждом таком ответе.Эта часть выполняется тегом шаблона.
-
Для всех входящих запросов, не использующих HTTP GET, HEAD, OPTIONS или TRACE, должен быть присутствующим и корректным куки CSRF и поле ‘csrfmiddlewaretoken’. Если этого нет, пользователю будет возвращён ошибка 403.
При валидации значения поля ‘csrfmiddlewaretoken’ сравнивается только секрет, а не весь токен, с секретом в значении куки. Это позволяет использовать постоянно меняющиеся токены. Хотя каждый запрос может использовать свой собственный токен, секрет остаётся общим для всех.
Эта проверка выполняется
CsrfViewMiddleware. -
Кроме того, для HTTPS-запросов жёсткая проверка referer выполняется
CsrfViewMiddleware. Это означает, что даже если домен-поддомен может устанавливать или изменять куки в вашем домене, он не может заставить пользователя отправить 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 за пределы текущего хоста или домена куки можно выполнить с помощью настройки
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 Strict Transport Security. Она также предполагает валидацию заголовка HOST и отсутствие уязвимостей XSS на вашем сайте (потому что уязвимости XSS уже позволяют злоумышленнику делать всё, что позволяет уязвимость CSRF, и многое другое).
Удаление заголовка Referer
Чтобы избежать раскрытия URL-адреса referer сторонним сайтам, вы можете отключить referer на тегах <a> вашего сайта. Например, вы можете использовать тег <meta name="referrer" content="no-referrer"> или добавить заголовок Referrer-Policy: no-referrer. Из-за жёсткой проверки referer в защите CSRF для HTTPS-запросов эти методы вызовут ошибку CSRF для запросов с «опасными» методами. Вместо этого используйте альтернативы, такие как <a rel="noreferrer" ...>" для ссылок на сторонние сайты.
Кеширование
Если тег шаблона csrf_token используется в шаблоне (или функция get_token вызывается каким-либо другим способом), CsrfViewMiddleware добавит в ответ куки и заголовок Vary: Cookie. Это означает, что middleware будет хорошо работать с кеш-middleware, если он используется так, как указано (UpdateCacheMiddleware идёт перед всем остальным middleware).
Однако, если вы используете декораторы кэша на отдельных представлениях, CSRF-middleware ещё не успеет установить заголовок 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 обычно будет значительным препятствием для тестирования функций представлений из-за необходимости токена CSRF, который должен отправляться с каждым POST-запросом. По этой причине HTTP-клиент Django для тестов был изменён, чтобы устанавливать флаг на запросах, ослабляющий middleware и декоратор csrf_protect, чтобы они больше не отклоняли запросы. Во всех других отношениях (например, отправка куки и т. д.) они ведут себя одинаково.
Если по какой-то причине вы хотите, чтобы тестовый клиент выполнял проверки CSRF, вы можете создать экземпляр тестового клиента, который выполняет проверки CSRF:
>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
Ограничения CSRF
Поддомены на сайте смогут устанавливать куки для клиента для всего домена. Установив куки и используя соответствующий токен, поддомены смогут обойти защиту CSRF. Единственный способ избежать этого - убедиться, что поддомены контролируются надёжными пользователями (или, по крайней мере, не могут устанавливать куки). Обратите внимание, что даже без CSRF есть другие уязвимости, такие как фиксация сессий, которые делают предоставление поддоменов ненадежным сторонам плохой идеей, и эти уязвимости трудно исправить в современных браузерах.
Случаи, требующие особого внимания
Некоторые представления могут иметь необычные требования, которые означают, что они не соответствуют описанному здесь обычному шаблону. В таких ситуациях могут быть полезны несколько утилит. Сценарии, в которых они могут потребоваться, описаны в следующем разделе.
Утилиты
Примеры ниже предполагают, что вы используете представления на основе функций. Если вы работаете с представлениями на основе классов, вы можете обратиться к Декорирование представлений на основе классов.
-
csrf_exempt(view) -
Этот декоратор отмечает представление как освобождённое от защиты, обеспечиваемой middleware. Пример:
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)
-
Этот декоратор принуждает представление к отправке куки 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_SAMESITECSRF_COOKIE_SECURECSRF_FAILURE_VIEWCSRF_HEADER_NAMECSRF_TRUSTED_ORIGINSCSRF_USE_SESSIONS
Часто задаваемые вопросы
Является ли отправка произвольной пары токенов CSRF (cookie и данные POST) уязвимостью?
Нет, это сделано по умолчанию. Без атаки «человек посередине» злоумышленник не может отправить cookie токена CSRF в браузере жертвы, поэтому для успешной атаки необходимо получить cookie браузера жертвы через XSS или аналогично, в этом случае злоумышленнику обычно не нужны атаки CSRF.
Некоторые инструменты аудита безопасности отмечают это как проблему, но, как упоминалось ранее, злоумышленник не может украсть cookie CSRF браузера пользователя. «Кража» или изменение своего собственного токена с помощью Firebug, инструментов разработчика Chrome и т. п. не является уязвимостью.
Является ли проблемой то, что защита CSRF Django по умолчанию не связана с сессией?
Нет, это сделано по умолчанию. Отсутствие связи защиты 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/3.2/ref/csrf/