Пожалуйста, помогите мне понять, почему я не должен сохранять токены CSRF в хранилище сеансов
Я новичок в веб-разработке и написал несколько тестов в Playstation, чтобы протестировать свое веб-приложение. Тесты завершаются неудачей только в Webkit/safari, по-видимому, из-за проблем с проверкой CSRF, которые возвращают 403 ошибки только в небезопасных запросах (POST/PUT/DELETE).
Серверная часть: api.example.com (Django) Интерфейс: app.example.com (React.js)
Моя текущая процедура:
- Пользователь вызывает выделенную конечную точку api.example.com/api/auth/csrf/, которая просто устанавливает файл cookie csrf, используя поведение Django по умолчанию для Set-Cookie, и ничего не возвращает в теле ответа.
- При последующем POST-запросе файл cookie токена csrf считывается и устанавливается в качестве заголовка X-CSRFToken.
- Пользователь входит в систему, используя POST-запрос и имя пользователя-пароль вместе с этим заголовком, чтобы получить идентификатор сеанса в файлах cookie.
- Последующие запросы 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
) это наиболее надежное решение, поскольку это полностью позволяет избежать проблем с перекрестными поддоменами.