Spec-Zone.ru › Django 3.0

Защита от межсайтовых поддельных запросов

Средство промежуточного программного обеспечения CSRF и тег шаблона предоставляют простую в использовании защиту от межсайтовых поддельных запросов. Этот тип атаки происходит, когда вредоносный веб-сайт содержит ссылку, кнопку формы или какой-либо JavaScript, предназначенный для выполнения некоторого действия на вашем веб-сайте, используя учетные данные вошедшего в систему пользователя, который посещает вредоносный сайт в своем браузере. Также обрабатывается родственный тип атаки «CSRF входа», где атакующий сайт обманывает браузер пользователя, заставляя его войти на сайт с чужими учетными данными.

Первая защита от атак CSRF заключается в обеспечении того, чтобы запросы GET (и другие «безопасные» методы, как определено в RFC 7231#section-4.2.1) не имели побочных эффектов. Запросы через «небезопасные» методы, такие как POST, PUT и DELETE, могут быть защищены, выполнив шаги ниже.

Как его использовать

Чтобы воспользоваться защитой CSRF в своих представлениях, выполните следующие действия:

  1. Средство промежуточного программного обеспечения CSRF по умолчанию активировано в настройке MIDDLEWARE. Если вы переопределите эту настройку, помните, что 'django.middleware.csrf.CsrfViewMiddleware' должно предшествовать любому средству промежуточного программного обеспечения представления, которое предполагает, что атаки CSRF были устранены.

    Если вы его отключили, что не рекомендуется, вы можете использовать csrf_protect() для отдельных представлений, которые требуется защитить (см. ниже).

  2. В любом шаблоне, использующем форму POST, используйте тег csrf_token внутри элемента <form> , если форма предназначена для внутреннего URL-адреса, например:

    <form method="post">{% csrf_token %}
    

    Это не следует делать для форм POST, которые направлены на внешние URL-адреса, так как это приведет к утечке маркера CSRF, что приведет к уязвимости.

  3. В соответствующих функциях представления убедитесь, что используется 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 для своих представлений, как указано выше.

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-библиотеку 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_USE_SESSIONS или CSRF_COOKIE_HTTPONLY, вам необходимо включить маркер CSRF в ваш HTML и прочитать маркер из DOM с помощью JavaScript:

{% csrf_token %}
<script type="text/javascript">
// using jQuery
const 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 backend добавляет {{ 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 основана на следующих вещах:

  1. Файл cookie CSRF, основанный на случайном секретном значении, к которому другие сайты не имеют доступа.

    Этот файл cookie устанавливается CsrfViewMiddleware. Он отправляется с каждым ответом, вызвавшим django.middleware.csrf.get_token() (функция, используемая для внутреннего получения токена CSRF), если он ещё не был установлен в запросе.

    Для защиты от атак BREACH токен не просто секрет; к секрету добавляется случайная соль, используемая для его шифрования.

    По соображениям безопасности значение секрета меняется каждый раз при входе пользователя в систему.

  2. В скрытом поле формы с именем «csrfmiddlewaretoken» присутствует во всех исходящих формах POST. Значение этого поля снова является значением секрета с солью, которая добавляется к нему и используется для его шифрования. Соль перегенерируется при каждом вызове get_token(), чтобы значение поля формы изменялось в каждом таком ответе.

    Эта часть выполняется тэгом шаблона.

  3. Для всех входящих запросов, не использующих HTTP GET, HEAD, OPTIONS или TRACE, должен быть предоставлен файл cookie CSRF, а поле «csrfmiddlewaretoken» должно быть присутствующим и корректным. Если это не так, пользователю будет выведено сообщение об ошибке 403.

    При проверке значения поля «csrfmiddlewaretoken» сравнивается только секрет, а не весь токен, с секретом в значении файла cookie. Это позволяет использовать постоянно меняющиеся токены. Хотя каждый запрос может использовать свой собственный токен, секрет остаётся общим для всех.

    Эта проверка выполняется CsrfViewMiddleware.

  4. Кроме того, для запросов 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 должен совпадать с заголовком HTTP Host.

    Расширить список разрешённых referer за пределы текущего хоста или домена файлов 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 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 добавит в ответ файл 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)

Ограничения CSRF

Субдомены на сайте смогут устанавливать cookie на клиенте для всего домена. Установив cookie и используя соответствующий токен, субдомены смогут обойти защиту CSRF. Единственный способ избежать этого — убедиться, что субдомены контролируются доверенными пользователями (или, по крайней мере, не могут устанавливать cookie). Обратите внимание, что даже без 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)
ensure_csrf_cookie(view)

Этот декоратор заставляет представление отправлять файл 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_AGE
  • CSRF_COOKIE_DOMAIN
  • CSRF_COOKIE_HTTPONLY
  • CSRF_COOKIE_NAME
  • CSRF_COOKIE_PATH
  • CSRF_COOKIE_SAMESITE
  • CSRF_COOKIE_SECURE
  • CSRF_FAILURE_VIEW
  • CSRF_HEADER_NAME
  • CSRF_TRUSTED_ORIGINS
  • CSRF_USE_SESSIONS

Часто задаваемые вопросы

Является ли отправка произвольной пары токенов CSRF (куки и данные POST) уязвимостью?

Нет, это сделано по дизайну. Без атаки «человек посередине» злоумышленник не может отправить куки токена CSRF в браузер жертвы, поэтому успешная атака должна получить куки браузера жертвы через XSS или подобные методы, в этом случае злоумышленнику обычно не нужны атаки CSRF.

Некоторые инструменты аудита безопасности отмечают это как проблему, но, как упоминалось ранее, злоумышленник не может украсть куки 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.0/ref/csrf/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API