Spec-Zone.ru › Django 2.1

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

Средство промежуточного программного обеспечения 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

Рекомендуемым источником маркера является куки csrftoken , который будет установлен, если вы включили защиту CSRF для своих представлений, как описано выше.

Имя куки маркера CSRF по умолчанию csrftoken, но вы можете контролировать имя куки с помощью настройки CSRF_COOKIE_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 в шаблоне. Куки содержат канонический маркер; CsrfViewMiddleware отдаст предпочтение куки перед маркером в DOM. В любом случае вы гарантированно получите куки, если маркер присутствует в DOM, поэтому вы должны использовать куки!

Предупреждение

Если ваше представление не рендерит шаблон, содержащий тег шаблона csrf_token, Django может не установить куки маркера CSRF. Это часто встречается в случаях, когда формы динамически добавляются на страницу. Для решения этой проблемы Django предоставляет декоратор представления, который заставляет устанавливать куки: 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
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 с именами куки и заголовков:

$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 Forbidden», если входящий запрос не проходит проверки, выполненные 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. Это означает, что даже если субдомен может устанавливать или изменять cookies на вашем домене, он не может заставить пользователя отправить 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). Эти запросы никогда не должны иметь потенциально опасных побочных эффектов, и поэтому атака CSRF с запросом GET должна быть безвредной. RFC 7231 определяет 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, чтобы они больше не отклоняли запросы. Во всех остальных отношениях (например, отправка cookies и т. д.) они ведут себя одинаково.

Если по какой-то причине вы *хотите*, чтобы тестовый клиент выполнял проверки CSRF, вы можете создать экземпляр тестового клиента, который принудительно выполняет проверки CSRF:

>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)

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

Субдомены на сайте смогут устанавливать cookies на клиенте для всего домена. Установив cookie и используя соответствующий токен, субдомены смогут обойти защиту CSRF. Единственный способ избежать этого — гарантировать, что субдомены контролируются доверенными пользователями (или, по крайней мере, не могут устанавливать cookies). Обратите внимание, что даже без CSRF существуют другие уязвимости, такие как фиксация сеанса, которые делают предоставление субдоменов недоверенным сторонам плохой идеей, и эти уязвимости нелегко исправить в текущих браузерах.

Случаи отклонения от правил

Определённые представления могут иметь необычные требования, которые не соответствуют обычному шаблону, рассматриваемому здесь. В таких ситуациях могут оказаться полезными ряд утилит. Сценарии, в которых они могут понадобиться, описаны в следующем разделе.

Утилиты

В примерах ниже предполагается использование представлений на основе функций. Если вы работаете с представлениями на основе классов, вы можете обратиться к Декорирование представлений на основе классов.

csrf_exempt(view) [source]

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

Является ли проблемой то, что защита 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/2.1/ref/csrf/

Spec-Zone.ru

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