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

Я новичок в веб-разработке и написал несколько тестов в Playstation, чтобы протестировать свое веб-приложение. Тесты завершаются неудачей только в Webkit/safari, по-видимому, из-за проблем с проверкой CSRF, которые возвращают 403 ошибки только в небезопасных запросах (POST/PUT/DELETE).

Серверная часть: api.example.com (Django) Интерфейс: app.example.com (React.js)

Моя текущая процедура:

  1. Пользователь вызывает выделенную конечную точку api.example.com/api/auth/csrf/, которая просто устанавливает файл cookie csrf, используя поведение Django по умолчанию для Set-Cookie, и ничего не возвращает в теле ответа.
  2. При последующем POST-запросе файл cookie токена csrf считывается и устанавливается в качестве заголовка X-CSRFToken.
  3. Пользователь входит в систему, используя POST-запрос и имя пользователя-пароль вместе с этим заголовком, чтобы получить идентификатор сеанса в файлах cookie.
  4. Последующие запросы POST с использованием csrftoken и session_id в файлах cookie завершаются неудачей, так как мой React.js приложению не удается создать заголовок X-CSRFToken (пустой заголовок). Это происходит только для Webkit/Safari, а не для Firefox или Chromium.

Насколько я понимаю, несмотря на то, что файлы cookie отправляются по поддоменам, webkit не позволяет считывать файлы cookie из document.cookies в разных поддоменах при установке Samesite=lax, даже если для домена установлено значение .example.com. Это контрастирует с chromium и firefox, которые, по-видимому, допускают такое поведение.

Я подтвердил, что во всех браузерах файлы cookie корректно отправляются в небезопасных запросах между поддоменами и что атрибут Set-Cookie в заголовке первоначального ответа действительно содержит домен.example.com. Однако заголовок X-CSRFToken остается пустым в моем запросе POST на api.example.com.

Наилучшая практическая процедура Из того, что я вижу, наилучшим решением было бы разместить интерфейс и серверную часть на example.com/app и example.com/api , чтобы обойти эту проблему.

Вопрос: Однако я не понимаю, почему я не могу просто сохранить токен csrf в хранилище сеанса и использовать его для создания заголовка X-CSRFToken. Я не понимаю, почему это было бы более уязвимо, чем считывание файла cookie csrftoken из cookiestorage.

Мои рассуждения таковы:

  • Токену csrf уже присвоено значение httponly=false, что позволяет считывать его и впоследствии использовать для установки заголовка X-CSRFToken. Таким образом, csrftoken уже уязвим для XSS-атак с использованием рекомендованного подхода Django (https://docs.djangoproject.com/en/5.1/howto/csrf/)
  • К хранилищу сеансов невозможен междоменный доступ, и поэтому оно не добавляет больше уязвимостей, чем файл cookie samesite='lax'.
  • Файл cookie по-прежнему используется для проверки X-CSRFToken, который поддерживает межсайтовую защиту, встроенную в браузеры.

Что я упускаю?

https://www.blackduck.com/glossary/what-is-csrf.html#:~:text=A%20CSRF%20token%20is%20a,token%20for%20every%20user%20session. https://cheat лист series.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html

Использование SameSite=Lax или Strict для ваших сессионных файлов cookie является способом устранения уязвимостей CSRF. Токены CSRF также используются. Единственная причина, по которой вам все еще нужны токены CSRF, заключается в том, что вы поддерживаете сценарии, в которых не поддерживается тот же сайт, что и в старых браузерах. Добавление токенов CSRF не повредит, это своего рода защита, но она больше не является строго обязательной для обычных требований.

Однако я не понимаю, почему я не могу просто сохранить токен csrf в хранилище сеанса и использовать его для создания X-CSRFToken

Ваш внешний javascript-код не может быть прочитан непосредственно из сеанса, который находится строго на стороне сервера, поэтому я не знаю, как это будет работать.

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

Следует иметь в виду, что риск CSRF здесь заключается в том, что другой источник инициирует деструктивный запрос. Этот другой источник должен делать это вслепую, у них нет возможности прочитать текст HTTP-ответа.

Эта проблема возникает из-за более строгой обработки Safari/Webkit междоменных файлов cookie и доступа к document.cookie, даже с доменами SameSite=Lax и .example.com.

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

  • Токен CSRF уже доступен для JavaScript (HttpOnly = false) в настройках Django по умолчанию.

  • Сохранение его в sessionStorage (или localStorage) не менее безопасно, чем чтение из файлов cookie, поскольку оба подвержены тем же рискам XSS.

  • Защита CSRF по-прежнему работает, потому что Django проверяет токен на стороне сервера, используя cookie и заголовок X-CSRFToken — источник значения заголовка (cookie или хранилище) этого не меняет.

Однако обратите внимание:

  • Основным риском здесь является XSS, а не CSRF. Если злоумышленник может запустить JS на вашем сайте, он может считывать данные как из document.cookie, так и из sessionStorage.

  • Убедитесь, что ваше приложение имеет надежную защиту XSS (политика безопасности контента, экранирование выходных данных и т.д.).

Если вы обслуживаете свой интерфейс только с другого поддомена, переместите как интерфейс, так и серверную часть в один и тот же корневой домен (example.com/app, example.com/api) это наиболее надежное решение, поскольку это полностью позволяет избежать проблем с перекрестными поддоменами.

Вернуться на верх