FlawOpen

flawopen.com/Vulnerabilities/Session Fixation & Cookie Hardening

Session Fixation: Session ID Rotation and Cookie Security

High Severity CWE-384 Cryptography & Auth
💡 Plain English Explainer — The Baggage Claim Ticket

Imagine a traveler is tricked into using a baggage ticket number an attacker already knows. If the airline keeps that same number after check-in, the attacker may claim the bag. Session fixation has the same boundary failure: an application accepts a session identifier the attacker can make the victim use, then upgrades that identifier to an authenticated session without rotating it. Both conditions are required; a phishing link alone does not establish fixation unless the application accepts or can be made to set that identifier.

Target: Server-side session management and the identifier used by an authenticated browser.
Precondition: The attacker can make the victim use a known session ID, for example through a supported URL-based ID or a cookie-injection weakness.
Failure: The application preserves that attacker-known ID across the unauthenticated-to-authenticated transition.
Remediation: Issue a new unpredictable session ID at authentication; configure cookie attributes for separate threats.

Core Concepts & Subsystem Terms

Session fixation
A session-management flaw in which an application accepts an identifier known to an attacker and keeps it when the victim authenticates.
Session ID rotation
Issue a fresh, unpredictable identifier when authentication changes session privilege. Preserve only the session data the application intends to keep.
Cookie attributes
Secure restricts cookie transmission to HTTPS; HttpOnly blocks JavaScript access to the cookie; SameSite controls some cross-site cookie sending. These flags address different risks and do not replace session-ID rotation.
__Host- prefix
In supporting browsers, this prefix requires a Secure cookie set from HTTPS, with Path=/ and no Domain attribute. It helps prevent a sibling subdomain from setting a parent-domain cookie with the same name.

Step-by-Step Attack Flow

Step 1

The attacker learns or chooses a session identifier

A vulnerable application or separate cookie-injection path lets the attacker obtain an identifier and make the victim browser send it. The exact delivery route depends on the application; an ordinary phishing URL does not work unless URL-based session IDs are accepted.

Step 2

The victim uses the attacker-known identifier

The browser sends that identifier to the application, which treats it as a valid anonymous session. The attacker must be able to recognize or reuse the same identifier.

Step 3

Authentication changes the session privilege

The victim signs in. If the application attaches the authenticated identity to the existing identifier instead of rotating it, that known identifier now refers to the authenticated session.

Step 4

The attacker reuses the unchanged identifier

If the attacker can still send the identifier and the server has not invalidated it, the attacker may access the victim’s session. Session-ID rotation at the privilege change breaks this chain.

The Mechanism & Root Cause

Session fixation requires both attacker influence over the identifier the victim uses and failure to replace that identifier when authentication succeeds. Cookie attributes are valuable defense in depth, but none substitutes for rotation: HttpOnly limits script reads, Secure limits transport to HTTPS, SameSite reduces some cross-site cookie delivery, and __Host- constrains cookie scope in supporting browsers.

login_view.py (Vulnerable pattern)Vulnerable
# 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 example)Hardened
# 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

The Attack & Exploit Sequence

Defensive Engineering & Prevention Rules

Sources and related security topics:Complete Security Directory →
CWE-384: Session Fixation → OWASP Session Management Cheat Sheet → OWASP WSTG: Testing for Session Fixation → Django Sessions: cycle_key() → MDN: HTTP Cookies and cookie prefixes →