Session hijacking начинается там, где чужой браузер получает доступ к вашей сессии: украденный cookie, утекший access token, старый refresh token или плохо настроенный HTTPS. Поэтому проверка HTTPS-заголовков и токенов — это не формальность, а быстрый способ понять, можно ли спокойно доверять авторизации в веб-сервисе.
На практике я смотрю две вещи. Первая — канал: сайт должен работать только по HTTPS, без смешанного контента и слабых редиректов. Вторая — сами сессионные данные: где хранится токен, какие у него атрибуты, как он обновляется и что происходит после logout, смены пароля или истечения срока жизни.
- Что именно нужно защитить
- Сначала проверьте HTTPS, а уже потом токены
- Какие HTTPS-заголовки смотреть в первую очередь
- Как читать заголовки в браузере без лишней магии
- Проверка токенов: не только наличие, но и жизненный цикл
- Где хранить токен: выбор под архитектуру
- Что выбрать в зависимости от ситуации
- Частые ошибки, из-за которых сессию всё равно крадут
- Минимальный набор, который стоит заложить в сервис
- Как понять, что проверка прошла нормально
- Итог
Что именно нужно защитить
В большинстве веб-сервисов сессия держится на одном из трёх вариантов:
- session id в cookie, который сервер связывает с записью в базе;
- JWT или другой self-contained token, который сервер проверяет по подписи и claims;
- пара access token + refresh token, где короткий токен даёт доступ, а длинный — обновляет его.
Разница есть, но риск один: если злоумышленник получил действующий идентификатор сессии, он может зайти как пользователь без логина и пароля. Поэтому задача защиты — не просто «сделать HTTPS», а сделать так, чтобы токен было сложно перехватить, сложно украсть через XSS, нельзя было использовать после logout и нельзя было незаметно продлевать бесконечно.
Сначала проверьте HTTPS, а уже потом токены
Если сайт доступен по HTTP, часть защиты токенов теряет смысл. Даже если cookie помечен как Secure, без правильного редиректа и HSTS первый запрос пользователя может уйти по небезопасному каналу. Для проверки не нужны сложные инструменты — достаточно браузера и пары команд.
- Откройте сайт по HTTPS:
https://example.com. - Проверьте, что в адресной строке нет предупреждений сертификата.
- Откройте
http://example.comи убедитесь, что есть редирект на HTTPS. - В DevTools во вкладке Network посмотрите, нет ли запросов по
http://внутри страницы. - Проверьте заголовки ответа командой
curl -I https://example.com.
curl -I https://example.com
curl -I http://example.com
Нормальная картина: HTTP-запрос возвращает редирект на HTTPS, а HTTPS-ответ содержит защитные заголовки и корректные Set-Cookie. Плохая картина: HTTP остаётся доступным, часть скриптов или картинок грузится по HTTP, а cookie уходит без флага Secure.
Какие HTTPS-заголовки смотреть в первую очередь
Не все security headers одинаково полезны против session hijacking. Некоторые защищают канал, другие уменьшают риск XSS, третьи не дают токену утечь через кэш или referrer. Ниже — заголовки, которые я обычно проверяю в первую очередь.
| Заголовок или настройка | Как должно выглядеть | Плохой признак | Какой риск закрывает |
|---|---|---|---|
Strict-Transport-Security |
max-age=31536000 или больше. includeSubDomains — только если все поддомены уже на HTTPS. |
Заголовка нет, маленький max-age, включён includeSubDomains на неподготовленных поддоменах. |
Снижает риск downgrade-атак и повторного перехода на HTTP. |
Set-Cookie: Secure |
Все auth-cookie отправляются только по HTTPS. | Cookie без Secure. |
Не даёт браузеру отправить cookie по HTTP. |
Set-Cookie: HttpOnly |
Auth-cookie недоступен для JavaScript. | Сессионный cookie читается через document.cookie. |
Уменьшает ущерб от XSS, но не заменяет защиту от XSS. |
Set-Cookie: SameSite |
Обычно Lax или Strict. None — только для кросс-сайтовых сценариев и обязательно с Secure. |
Нет SameSite или None без необходимости. |
Ограничивает автоматическую отправку cookie на чужих сайтах. |
Content-Security-Policy |
Ограниченные источники для скриптов, стилей и iframe. Без лишних * и unsafe-inline, если можно без них. |
Нет CSP или политика разрешает всё подряд. | Снижает риск кражи токена через XSS. |
Cache-Control |
Для страниц и ответов с токенами: no-store. |
Auth-страницы сохраняются в кэше браузера или прокси. | Не даёт токену остаться в кэше после logout. |
Referrer-Policy |
no-referrer или strict-origin-when-cross-origin. |
Полный URL с токенами уходит во внешних referer. | Снижает риск утечки токенов из query string. |
Access-Control-Allow-Origin |
Для API — конкретные разрешённые origin. | * для API с credentials или слишком широкий список доменов. |
Ограничивает доступ к API с чужих сайтов. |
Пример хорошего набора заголовков для страницы авторизованного пользователя может выглядеть так:
HTTP/2 200
strict-transport-security: max-age=31536000; includeSubDomains
content-security-policy: default-src 'self'
referrer-policy: strict-origin-when-cross-origin
cache-control: no-store
set-cookie: sid=...; Secure; HttpOnly; SameSite=Lax; Path=/
Не копируйте реальные значения токенов в отчёты, чаты или тикеты поддержки. Для проверки достаточно увидеть, что cookie есть, атрибуты выставлены правильно, а сервер отдаёт нужные заголовки.
Не проверяйте безопасность сервиса, вставляя реальные токены в curl-команды, скриншоты, логи и сообщения коллегам. Такие данные нужно скрывать так же, как пароли.
Как читать заголовки в браузере без лишней магии
DevTools даёт достаточно информации для первичной проверки. Откройте вкладку Network, обновите страницу и выберите главный HTML-документ. Смотрите не только статус ответа, но и раздел Headers.
- Проверьте scheme: запрос должен идти по
https. - Откройте Response Headers и найдите HSTS, CSP, Cache-Control и Referrer-Policy.
- В Request Headers посмотрите, не уходит ли токен в URL, Referer или лишних заголовках.
- В Cookies проверьте атрибуты session cookie: Secure, HttpOnly, SameSite, Path.
- Перейдите на страницу с API-запросами и проверьте, как отправляется токен: cookie или
Authorization: Bearer ....
Если токен виден в URL вида /callback?access_token=... или /reset?token=..., это красный флаг. Такие токены попадают в историю браузера, логи прокси, аналитику и Referer. Для одноразовых ссылок ещё допустим короткий срок жизни, но для постоянных сессий так делать нельзя.
Проверка токенов: не только наличие, но и жизненный цикл
Хороший токен — это не просто длинная строка. У него должны быть понятные правила: когда создан, когда истекает, как обновляется, что происходит при logout и как сервер понимает, что использовать его больше нельзя.
- Session id в cookie. Лучше, когда cookie содержит случайный opaque-идентификатор, а все данные сессии лежат на сервере. Тогда можно отозвать сессию одним действием.
- JWT access token. Подпись не равна секретности. JWT может быть подписан, но всё равно читаться всеми. Не кладите туда пароли, паспортные данные и лишнюю чувствительную информацию.
- Refresh token. Должен жить дольше access token, но обязан обновляться. Хороший вариант — refresh token rotation: при каждом обновлении выдаётся новый refresh token, а старый становится недействительным.
- CSRF token. Это не токен авторизации. Он защищает от поддельных запросов, но не заменяет Secure, HttpOnly и SameSite.
- Токен в query string. Почти всегда плохая идея для сессий. Если без этого нельзя, срок жизни должен быть коротким, а область применения — очень узкой.
Для JWT я бы обязательно проверял не только подпись. Сервер должен проверять exp, nbf, iss, aud, sub и разрешённые алгоритмы. Отдельно запретите none и не принимайте алгоритм, который пришёл из токена без проверки на стороне сервера.
Где хранить токен: выбор под архитектуру
| Вариант | Когда подходит | Плюсы | Риски | Что проверить |
|---|---|---|---|---|
| HttpOnly + Secure cookie с server-side session | Классические сайты, админки, сервисы с HTML-страницами. | JavaScript не читает cookie, сессию легко отозвать на сервере. | Нужна защита от CSRF и XSS. | Secure, HttpOnly, SameSite, Path, timeout, invalidation после logout. |
| HttpOnly + Secure cookie с JWT | SPA или API, где не хочется хранить токен в localStorage. | Меньше риска кражи токена через JS. | JWT сложнее отозвать до истечения срока. | Короткий access token, refresh rotation, blacklist или token introspection при необходимости. |
| Bearer token в localStorage | Простые SPA, внутренние инструменты, где скорость разработки важнее максимальной защиты. | Просто реализовать, удобно для API-клиентов. | Любой XSS может прочитать токен. | CSP, короткий срок жизни, refresh rotation, строгий CORS. |
| Token in memory | Сценарии, где потеря токена после перезагрузки страницы приемлема. | Токен не лежит в постоянном хранилище. | Пользователю придётся входить чаще. | Обновление токена, защита вкладок, поведение после refresh страницы. |
| Токен в URL | Только временные одноразовые ссылки: сброс пароля, подтверждение email. | Просто для пользователя. | Попадает в историю, логи, Referer и скриншоты. | Короткий срок жизни, одноразовость, запрет для постоянных сессий. |
Что выбрать в зависимости от ситуации
- Если у вас обычный сайт с формами входа и HTML-страницами, берите server-side session в Secure HttpOnly cookie, добавьте SameSite=Lax или Strict и отдельный CSRF token для опасных действий.
- Если у вас SPA и отдельный API, лучший вариант — Backend For Frontend: фронт ходит к своему backend, а backend уже общается с API. Если BFF нет, используйте короткие access token, refresh rotation и не храните лишние данные в токене.
- Если у вас публичный API для мобильных приложений, используйте OAuth-сценарии с PKCE, refresh token rotation, scope и audience. Для backend-to-backend запросов рассмотрите mTLS или доверенные сервисные ключи с ограничением по сети.
- Если это админка, финансовый кабинет или сервис с высоким риском, добавьте короткий idle timeout, повторную авторизацию для чувствительных операций, список активных сессий и принудительный logout всех устройств после смены пароля.
- Если сервис legacy и быстро переписать архитектуру нельзя, начните с HTTPS-редиректа, HSTS, Secure/HttpOnly/SameSite cookie, запрета токенов в URL и проверки logout. Это даст заметный эффект без большого рефакторинга.
Частые ошибки, из-за которых сессию всё равно крадут
- HTTPS есть, но HTTP-версия сайта продолжает отвечать 200 OK.
- Cookie помечен как Secure, но нет HSTS, и первый запрос можно увести на HTTP.
- HttpOnly считают полной защитой от XSS. На деле он только не даёт JavaScript прочитать cookie.
- SameSite=Strict включают без проверки сценариев оплаты, SSO и внешних редиректов, а потом ломают вход.
- JWT живёт неделями, а отозвать его нельзя даже после смены пароля.
- Refresh token не обновляется и может использоваться бесконечно.
- Access token попадает в URL, а потом в логи аналитики и прокси.
- После logout старый токен всё ещё принимается сервером.
- CORS настроен слишком широко, особенно для API, которое принимает credentials.
- Auth-страницы и ответы API с токенами сохраняются в кэше.
- В логах остаются заголовки
CookieиAuthorization.
Минимальный набор, который стоит заложить в сервис
- Принудительный HTTPS для всех auth-маршрутов и всего приложения.
- Редирект HTTP → HTTPS и HSTS с нормальным
max-age. - Cookie с атрибутами
Secure,HttpOnly,SameSiteи узкимPath. - Регенерация session id после входа, смены пароля, включения 2FA и повышения прав.
- Короткий срок жизни access token и понятная схема обновления refresh token.
- Отзыв токенов после logout и после смены критичных данных пользователя.
Cache-Control: no-storeдля страниц и ответов, где есть токены.- CSP, Referrer-Policy и запрет токенов в URL.
- Строгий CORS для API: конкретные origin, конкретные методы и заголовки.
- Санитизация логов: никаких реальных cookie, access token и refresh token в логах.
Как понять, что проверка прошла нормально
После настройки полезно пройтись по нескольким сценариям руками. Это быстрее вскрывает проблемы, чем абстрактный чеклист.
- После входа токен появился, атрибуты cookie правильные.
- После logout старый токен возвращает 401 или отправляет на вход.
- После смены пароля старые refresh token и сессии больше не работают.
- После idle timeout сервис просит войти заново.
- При повторном использовании refresh token после rotation сервер отказывает.
- API не принимает запросы с неизвестных origin.
- В логах не видно реальных значений токенов.
- В истории браузера не остаётся URL с access token.
- DevTools не показывает mixed content по HTTP.
- Страницы авторизации не сохраняются в кэше.
Итог
Защита от session hijacking строится не на одном «секретном» заголовке, а на связке: HTTPS без исключений, правильные cookie-атрибуты, короткий жизненный цикл токенов, отзыв сессий и защита от утечек через XSS, кэш, Referer и CORS.
Если нужно сделать быстро, начните с самого практичного: HTTPS-редирект, HSTS, Secure/HttpOnly/SameSite cookie, запрет токенов в URL, no-store для auth-ответов, refresh token rotation и проверка logout. Это закрывает большую часть реальных инцидентов без усложнения архитектуры.
