Защита от session hijacking: что проверять в HTTPS-заголовках и токенах

Session hijacking начинается там, где чужой браузер получает доступ к вашей сессии: украденный cookie, утекший access token, старый refresh token или плохо настроенный HTTPS. Поэтому проверка HTTPS-заголовков и токенов — это не формальность, а быстрый способ понять, можно ли спокойно доверять авторизации в веб-сервисе.

На практике я смотрю две вещи. Первая — канал: сайт должен работать только по HTTPS, без смешанного контента и слабых редиректов. Вторая — сами сессионные данные: где хранится токен, какие у него атрибуты, как он обновляется и что происходит после logout, смены пароля или истечения срока жизни.

Что именно нужно защитить

В большинстве веб-сервисов сессия держится на одном из трёх вариантов:

  • session id в cookie, который сервер связывает с записью в базе;
  • JWT или другой self-contained token, который сервер проверяет по подписи и claims;
  • пара access token + refresh token, где короткий токен даёт доступ, а длинный — обновляет его.

Разница есть, но риск один: если злоумышленник получил действующий идентификатор сессии, он может зайти как пользователь без логина и пароля. Поэтому задача защиты — не просто «сделать HTTPS», а сделать так, чтобы токен было сложно перехватить, сложно украсть через XSS, нельзя было использовать после logout и нельзя было незаметно продлевать бесконечно.

Сначала проверьте HTTPS, а уже потом токены

Если сайт доступен по HTTP, часть защиты токенов теряет смысл. Даже если cookie помечен как Secure, без правильного редиректа и HSTS первый запрос пользователя может уйти по небезопасному каналу. Для проверки не нужны сложные инструменты — достаточно браузера и пары команд.

  1. Откройте сайт по HTTPS: https://example.com.
  2. Проверьте, что в адресной строке нет предупреждений сертификата.
  3. Откройте http://example.com и убедитесь, что есть редирект на HTTPS.
  4. В DevTools во вкладке Network посмотрите, нет ли запросов по http:// внутри страницы.
  5. Проверьте заголовки ответа командой 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.

  1. Проверьте scheme: запрос должен идти по https.
  2. Откройте Response Headers и найдите HSTS, CSP, Cache-Control и Referrer-Policy.
  3. В Request Headers посмотрите, не уходит ли токен в URL, Referer или лишних заголовках.
  4. В Cookies проверьте атрибуты session cookie: Secure, HttpOnly, SameSite, Path.
  5. Перейдите на страницу с 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.

Минимальный набор, который стоит заложить в сервис

  1. Принудительный HTTPS для всех auth-маршрутов и всего приложения.
  2. Редирект HTTP → HTTPS и HSTS с нормальным max-age.
  3. Cookie с атрибутами Secure, HttpOnly, SameSite и узким Path.
  4. Регенерация session id после входа, смены пароля, включения 2FA и повышения прав.
  5. Короткий срок жизни access token и понятная схема обновления refresh token.
  6. Отзыв токенов после logout и после смены критичных данных пользователя.
  7. Cache-Control: no-store для страниц и ответов, где есть токены.
  8. CSP, Referrer-Policy и запрет токенов в URL.
  9. Строгий CORS для API: конкретные origin, конкретные методы и заголовки.
  10. Санитизация логов: никаких реальных 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. Это закрывает большую часть реальных инцидентов без усложнения архитектуры.

Оцените статью
PEFile — Безопасность и технологии простым языком