Spec-Zone.ru › Django 4.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 Cookie js-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>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>

Установка маркера в AJAX-запросе

Наконец, вам нужно будет установить заголовок в вашем AJAX-запросе. Используя API fetch():

const request = new Request(
    /* URL */,
    {
        method: 'POST',
        headers: {'X-CSRFToken': csrftoken},
        mode: 'same-origin' // Do not send CSRF token to another domain.
    }
);
fetch(request).then(function(response) {
    // ...
});

Использование CSRF в шаблонах Jinja2

Django backend шаблонов 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 основана на следующих вещах:

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

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

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

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

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

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

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

    При проверке значения поля ‘csrfmiddlewaretoken’ сравнивается только секрет, а не весь токен, с секретом в значении куки. Это позволяет использовать постоянно меняющиеся токены. Хотя каждый запрос может использовать свой токен, секрет остается общим для всех.

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

  4. CsrfViewMiddleware проверяет заголовок Origin, если он предоставлен браузером, по отношению к текущему хосту и настройке CSRF_TRUSTED_ORIGINS. Это обеспечивает защиту от атак через разные поддомены.
  5. Кроме того, для запросов HTTPS, если заголовок Origin не предоставлен, CsrfViewMiddleware выполняет строгую проверку referer. Это означает, что даже если поддомен может установить или изменить куки на вашем домене, он не может заставить пользователя отправить данные на ваше приложение, так как этот запрос не будет исходить из вашего точного домена.

    Это также решает проблему атаки «человек посередине», которая возможна при использовании 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 за пределы текущего хоста или домена куки можно осуществить с помощью настройки CSRF_TRUSTED_ORIGINS.

Новое в Django 4.0:

Origin проверка была добавлена, как описано выше.

Это гарантирует, что формы, которые исходят только из надёжных доменов, могут использоваться для отправки данных 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)
ensure_csrf_cookie(view)

Этот декоратор заставляет представление отправлять куки 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 для своих представлений.

Настройки

Существует ряд настроек, которые можно использовать для управления поведением Django в отношении CSRF:

  • 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 (cookie и данных 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/4.0/ref/csrf/

Spec-Zone.ru

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