Безопасность Cookie: как на самом деле работают XSS и CSRF
В посте про заголовки в HTTP-методах разобрали HttpOnly, Secure, SameSite только на уровне определений. Разберём, от каких атак они защищают и почему одного атрибута обычно недостаточно.
📌 XSS: кража куки через чужой JavaScript
Если на сайте есть уязвимость, позволяющая внедрить свой JS-код (например, через незаэкранированный ввод в комментарии), этот код выполняется в браузере жертвы с полным доступом к document.cookie и отправляет куки на сервер атакующего.
HttpOnly - единственная защита от этого сценария. Кука с этим флагом недоступна через document.cookie, поэтому даже успешная XSS её не прочитает
Важно понимать границу - HttpOnly защищает только куку, а не всю страницу. Если у приложения есть XSS, атакующий всё равно может выполнять произвольные действия от имени пользователя прямо в открытой вкладке, просто не сможет унести сессионный токен с собой
📌 CSRF: заставить браузер жертвы отправить запрос без его ведома
Пользователь залогинен на банковском сайте и переходит по ссылке на вредоносный сайт со скрытой формой, которая автоматически отправляет POST-запрос (например, перевод денег). Браузер прикладывает куки к любому запросу на нужный домен независимо от того, откуда он инициирован - сервер видит валидную куку и выполняет операцию.
HttpOnly здесь не помогает - атакующему не нужно читать куку, браузер сам её прикрепит
Именно для этого нужен SameSite
📌 SameSite: как обстоят дела в 2026 году
Браузеры с 2020 года применяют SameSite=Lax по умолчанию, но для чувствительных приложений атрибут стоит указывать явно.
Strict - кука не отправляется при межсайтовых запросах вообще, включая переход по ссылке. Полная защита от CSRF, но может разлогинить пользователя при переходе из письма
Lax - кука отправляется при обычной навигации, но не при кросс-доменных POST, iframe/img или fetch с других доменов. Практический баланс, поэтому и стал дефолтом
None - кука отправляется всегда, требует Secure (иначе браузер её отбросит). Нужен для виджетов на сторонних доменах, но сам по себе не защищает от CSRF
📌 Почему одного SameSite недостаточно
SameSite не защищает от атак между поддоменами (same-site, но cross-origin). Актуальный подход - комбинация слоёв: SameSite на сессионных куках как база, валидация заголовка Origin на запросах, меняющих состояние, требование кастомного заголовка (простой межсайтовый POST не может его добавить без preflight), и CSRF-токен для по-настоящему чувствительных операций.
🌿 Ловушки для QA
1) Проверь, что сессионная кука имеет HttpOnly - открой DevTools, вкладку Application, и убедись, что кука не отображается в консоли через document.cookie
2) Проверь поведение при SameSite=None без Secure - кука должна быть молча отброшена браузером, а не просто не сработать через ошибку
3) Проверь CSRF-защиту вручную - попробуй создать HTML-страницу с автоотправляющейся формой на чувствительный эндпоинт и открыть её в браузере с активной сессией
4) Проверь, что критичные операции (смена пароля, перевод денег) защищены дополнительным слоем поверх SameSite - например, CSRF-токеном или подтверждением через отдельный канал
5) Проверь поведение SameSite=Strict на сценариях с внешними ссылками (переход из email, из другого сайта) - пользователь не должен неожиданно вылетать из сессии
6) Если в приложении используются поддомены - проверь, что куки не передаются между ними по умолчанию, same-site политика не различает поддомены автоматически
7) Если сессию нужно расшарить между поддоменами - проверь, что атрибут Domain указан явно (например, .example.com), а не оставлен на дефолт
Поставь 🔥, если полезно
Post #541
1.91K

- 🔥 39