FlawOpen

flawopen.com/Vulnerabilities/Фиксация сессии и защита cookie

Фиксация сессии: смена ID при входе (CWE-384)

High Severity CWE-384 Cryptography & Auth
💡 Простое объяснение — приклеенная багажная квитанция

Представьте, что путешественника убеждают воспользоваться номером багажной квитанции, который уже известен атакующему. Если авиакомпания сохранит этот номер после регистрации багажа, атакующий сможет забрать чемодан. При фиксации сессии приложение принимает идентификатор, который атакующий может навязать жертве, а затем без смены превращает его в аутентифицированную сессию. Нужны оба условия; обычная фишинговая ссылка сама по себе не фиксирует сессию, если приложение не принимает этот идентификатор и не позволяет его установить.

Цель: управление серверными сессиями и идентификатор аутентифицированного браузера.
Предусловие: атакующий может заставить жертву использовать известный ему ID сессии, например через принимаемый приложением ID в URL или внедрение cookie.
Ошибка: приложение сохраняет известный атакующему ID при переходе от анонимной сессии к аутентифицированной.
Исправление: при аутентификации выдавать новый непредсказуемый ID; настраивать атрибуты cookie для отдельных угроз.

Основные понятия и термины подсистемы

Фиксация сессии
Ошибка управления сессиями: приложение принимает известный атакующему идентификатор и сохраняет его после аутентификации жертвы.
Смена ID сессии
Выдавайте новый непредсказуемый идентификатор при повышении привилегий сессии. Сохраняйте только те данные сессии, которые действительно нужны приложению.
Атрибуты cookie
Secure ограничивает передачу cookie протоколом HTTPS; HttpOnly запрещает чтение cookie из JavaScript; SameSite управляет частью межсайтовой отправки cookie. Эти атрибуты решают разные задачи и не заменяют смену ID.
Префикс __Host-
В поддерживающих браузерах требует установить Secure-cookie через HTTPS с Path=/ и без атрибута Domain. Это помогает не дать соседнему поддомену установить одноимённую cookie для родительского домена.

Пошаговая последовательность атаки

Step 1

Атакующий получает или выбирает идентификатор сессии

Отдельная уязвимость или функция позволяет атакующему получить ID и заставить браузер жертвы отправлять его. Способ зависит от приложения; обычная фишинговая ссылка не сработает, если приложение не принимает ID сессии в URL.

Step 2

Жертва использует известный атакующему идентификатор

Браузер отправляет ID приложению, которое считает его допустимой анонимной сессией. Атакующий должен иметь возможность распознать или повторно использовать тот же ID.

Step 3

Аутентификация повышает привилегии сессии

Жертва входит в систему. Если приложение связывает аутентифицированную личность с прежним ID, не меняя его, этот известный идентификатор начинает обозначать аутентифицированную сессию.

Step 4

Атакующий повторно использует прежний ID

Если атакующий всё ещё может отправить ID и сервер его не аннулировал, возможно использование сессии жертвы. Смена ID при повышении привилегий разрывает эту цепочку.

Механизм и первопричина

Для фиксации сессии нужны и влияние атакующего на идентификатор, который использует жертва, и отсутствие его замены после входа. Атрибуты cookie дополняют защиту, но не заменяют смену ID: HttpOnly ограничивает чтение скриптами, Secure требует HTTPS, SameSite сокращает часть межсайтовых отправок, а __Host- ограничивает область cookie в поддерживающих браузерах.

login_view.py (Уязвимый шаблон)Уязвимый
# VULNERABLE: custom session code keeps the pre-authentication ID.
def login_view(request):
    user = authenticate(request, request.POST["username"],
                        request.POST["password"])
    if user is not None:
        # BUG: privilege changes, but the session ID does not.
        request.session["user_id"] = user.pk
        request.session["authenticated"] = True
        return redirect("/account/")
    return redirect("/login/")
login_view.py (Пример Django)Защищённый
# HARDENED: Django login() rotates the session key on authentication.
from django.contrib.auth import authenticate, login

def login_view(request):
    user = authenticate(request, request.POST["username"],
                        request.POST["password"])
    if user is not None:
        # Uses Django's supported authentication/session integration.
        login(request, user)
        return redirect("/account/")
    return redirect("/login/")

# settings.py: use HTTPS, host-only scope, and a deliberate SameSite policy.
SESSION_COOKIE_NAME = "__Host-sessionid"
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_PATH = "/"
SESSION_COOKIE_DOMAIN = None

Сценарий атаки и концептуальный пример

Инженерные правила предотвращения и защиты

Источники и смежные темы безопасности:Полный каталог безопасности →
CWE-384: фиксация сессии → OWASP: рекомендации по управлению сессиями → OWASP WSTG: проверка фиксации сессии → Django: сессии и cycle_key() → MDN: HTTP-cookie и префиксы →