Управление сессиями

После входа сервер выдаёт «пропуск» — и от того, как этот пропуск устроен и хранится, зависит, не воспользуется ли им чужой.

Сессия — состояние «пользователь вошёл», которое сервер связывает с непредсказуемым идентификатором (session ID); этот идентификатор браузер хранит обычно в cookie и присылает с каждым запросом.

Пароль вводят один раз, а дальше всю работу удостоверяет session ID. Поэтому он сам по себе — ценность: кто владеет действующим идентификатором, тот и есть пользователь для сервера. Угнать сессию (session hijacking) часто проще, чем подобрать пароль: достаточно перехватить cookie или подсунуть жертве заранее известный идентификатор. Управление сессиями — это набор мер, которые делают идентификатор трудным для кражи и быстро обесценивают украденный. Все примеры — на своих стендах; перехват чужих сессий наказуем (ст. 272 УК РФ).

Зачем это знать защитнику

Сломанное управление сессиями обнуляет качество остальной защиты: можно идеально хранить пароли и всё равно отдать аккаунт, если cookie доступна из JavaScript или идентификатор не меняется при входе. Разработчик, понимающий жизненный цикл сессии, ставит правильные флаги cookie и ротацию там, где их легко забыть; защитник по этим же признакам быстро оценивает риск.

Безопасные cookie: три флага

Идентификатор сессии почти всегда живёт в cookie, и три атрибута решают, насколько она защищена. Уязвимый вариант — cookie без флагов:

# УЯЗВИМО: cookie без защиты — доступна из JS, ходит по HTTP, шлётся кросс-сайтово
Set-Cookie: session=ab12cd34

HttpOnly запрещает доступ к cookie из JavaScript (document.cookie). Это значит, что даже сработавший XSS не сможет прочитать сессионный токен и отправить его атакующему. Secure разрешает отправлять cookie только по HTTPS — она не уйдёт в открытом виде по HTTP, где её можно перехватить в сети. SameSite ограничивает отправку cookie на сторонние сайты: Lax (разумный дефолт) или Strict заметно снижают риск CSRF, потому что cookie не прикрепляется к запросам, инициированным чужим сайтом. Безопасный набор:

# БЕЗОПАСНО: недоступна из JS, только HTTPS, ограничена кросс-сайтово
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/

В коде это обычно настройка фреймворка, а не ручная сборка заголовка:

# БЕЗОПАСНО: безопасные атрибуты cookie сессии (пример настроек)
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True       # только HTTPS
SESSION_COOKIE_SAMESITE = "Lax"

Фиксация сессии и ротация при логине

Фиксация сессии (session fixation) — атака, при которой жертве заранее навязывают известный атакующему session ID, и если после входа сервер не меняет идентификатор, атакующий оказывается внутри уже аутентифицированной сессии.

Логика атаки: злоумышленник получает валидный (ещё не залогиненный) идентификатор и заставляет браузер жертвы использовать именно его (например, через ссылку с этим ID). Жертва входит под своим паролем. Если сервер оставляет тот же session ID после успешного логина, то идентификатор, который знает атакующий, теперь привязан к авторизованной сессии — и он входит как жертва, не зная пароля.

Защита прямолинейна и обязательна: при каждой смене уровня привилегий (вход, повышение прав) выдавайте новый идентификатор сессии, аннулируя прежний. Тогда тот ID, что «зафиксировал» атакующий, после логина становится недействительным.

# БЕЗОПАСНО: ротация идентификатора сессии при входе
def login(request, user):
    authenticate(user)
    request.session.cycle_key()   # новый session ID, старый недействителен
    # ... отметить пользователя как вошедшего

Симметрично: при выходе сессию нужно уничтожать на сервере, а не просто удалять cookie у клиента. Иначе перехваченный ранее идентификатор продолжит работать.

Тайм-ауты: idle и absolute

Бессрочная сессия — подарок для того, кто украл идентификатор. Поэтому используют два тайм-аута. Idle timeout (по бездействию) завершает сессию после периода без активности — украденный токен «протухнет», если им не пользоваться. Absolute timeout (предельный срок жизни) ограничивает общую длительность сессии независимо от активности, заставляя периодически переаутентифицироваться. Конкретные значения зависят от чувствительности сервиса: для банка — минуты, для форума — дольше. Опция «запомнить меня» должна реализовываться отдельным долговременным токеном с ограниченными правами, а не превращать основную сессию в вечную.

Как это работает под капотом

Сервер хранит у себя сопоставление «session ID → данные сессии» (в памяти, Redis, БД), а клиенту отдаёт только сам идентификатор. Идентификатор должен быть длинным и криптослучайным (генерируется надёжным генератором, а не счётчиком и не предсказуемым значением), иначе его можно угадать. Браузер автоматически прикладывает cookie к каждому запросу на тот же домен — отсюда и удобство, и риск: вся защита сводится к тому, чтобы (1) идентификатор нельзя было предсказать, (2) его нельзя было прочитать или перехватить (HttpOnly, Secure), (3) его нельзя было навязать (ротация при логине) и (4) он быстро терял силу (тайм-ауты, аннулирование при выходе).

Как защититься

  • Криптослучайный идентификатор достаточной длины; генерацию доверьте фреймворку, не изобретайте свой.
  • Флаги cookie: HttpOnly + Secure + SameSite. Это базовый, не обсуждаемый минимум для сессионной cookie.
  • Ротация при логине и при смене прав (защита от фиксации); уничтожение сессии на сервере при выходе и при подозрении на компрометацию.
  • Idle и absolute тайм-ауты; «запомнить меня» — отдельным ограниченным токеном.
  • HTTPS на всём приложении (HSTS), чтобы cookie не утекала по открытому каналу.
  • Обнаружение. Один session ID с разных IP/устройств одновременно, резкая смена User-Agent или географии в рамках сессии — признаки угона. Дайте пользователю список активных сессий и кнопку «завершить все».

Юридическое напоминание: экспериментировать с фиксацией и угоном сессий можно только на своих стендах или в легальном пентесте с разрешением владельца.

Итоги

  • Session ID — это «пропуск» после входа; кто им владеет, тот и пользователь, поэтому его берегут.
  • Cookie сессии обязана иметь HttpOnly, Secure и SameSite; идентификатор — криптослучайный и длинный.
  • Ротация идентификатора при логине закрывает фиксацию сессии; выход должен уничтожать сессию на сервере.
  • Idle и absolute тайм-ауты обесценивают украденный токен; «запомнить меня» — отдельный токен.
  • Мониторьте аномалии (один ID, разные IP) и дайте пользователю управлять активными сессиями.
Проверьте себя
1. Что именно предотвращает ротация (смена) идентификатора сессии при успешном входе?
AМежсайтовый скриптинг (XSS)
BФиксацию сессии: навязанный заранее идентификатор после логина становится недействительным, и атакующий не попадает в авторизованную сессию
CПодбор пароля перебором
DSQL-инъекцию в форму логина
2. Зачем сессионной cookie флаг HttpOnly?
AОн шифрует содержимое cookie
BОн делает cookie недоступной из JavaScript, поэтому даже сработавший XSS не прочитает session ID через document.cookie
CОн запрещает отправлять cookie по HTTPS
DОн автоматически продлевает сессию