AppSec · Sep 23, 2026 · 7 min read

Session fixation: when the server trusts the ID the client presents

A server that accepts a pre-supplied session token and elevates it on login lets an attacker fix the ID before authentication and collect the authenticated session after.

Session fixation lets an attacker decide the token before the victim ever logs in. If the server promotes a pre-supplied identifier into an authenticated session rather than issuing a fresh one at login, the attacker who supplied the ID now holds a valid authenticated session - without knowing the victim's password and without intercepting any response.

01The three-step fixation pattern

The attack unfolds in sequence. First, the attacker obtains a valid unauthenticated session token from the target - by visiting the login page, for example. Second, the attacker delivers that token to the victim's browser: through a URL parameter the application copies into a cookie, through a sub-domain cookie-set if the same-site boundary is loose, or through a meta-refresh redirect that carries the token in the query string. Third, the victim authenticates normally. If the server does not rotate the session identifier at that moment, the attacker's pre-placed token is now the victim's authenticated session - collectable immediately.

Attacker ──► GET /login ──► session=XYZ (unauthenticated) ──► deliver token to victim (URL param, cookie, redirect) Victim ──► POST /login (credentials) ──► server keeps session=XYZ session=XYZ is now authenticated Attacker ──► GET /account Cookie: session=XYZ ──► 200 OK - victim's account
Session fixation in three steps. The token never changes; only its privilege level does.

02Confirming the vulnerability without impersonating any real user

The confirming probe is a two-step differential. In the first request, issue a login with a controlled test account while presenting a pre-chosen session identifier in the Cookie header. If the response does not set a new session cookie - or sets one with the same value - the server did not rotate. In the second request, send a state-revealing request (account profile, dashboard) with the original pre-chosen token from a separate, cookie-clean session. If the server returns the authenticated profile for the test account, the boundary is confirmed open. The test account is controlled by the probe; no real user session is touched and no credential is harvested.

session-fixation-probe.httphttp
# Step 1 - login with a pre-chosen token and controlled credentials
POST /login HTTP/1.1
Cookie: session=apNonce62615533
Content-Type: application/x-www-form-urlencoded

username=probe-user&password=probe-pass

HTTP/1.1 302 Found
Location: /dashboard
# No Set-Cookie - server kept the pre-supplied token. Fixation confirmed.

# Step 2 - use the same token from a clean session (no cookies sent before)
GET /dashboard HTTP/1.1
Cookie: session=apNonce62615533

HTTP/1.1 200 OK
X-User: probe-user   <-- authenticated, token never rotated

A rotation-aware server returns a Set-Cookie with a fresh, unpredictable value in the login response. The absence of that header - or a header echoing the pre-supplied value - is the confirming artifact. No brute-force, no real victim interaction, and no interception of any response the server generated for a real user.

Proven - HighSession fixation - token not rotated at login - POST /login
Step 1: POST /login Cookie: session=apNonce62615533 (controlled test account) ──► 302 Found No Set-Cookie in response Step 2: GET /dashboard Cookie: session=apNonce62615533 (clean session, no prior cookies) ──► 200 OK X-User: probe-user (authenticated - token promoted without rotation)
Oracle: differential two-step. Controlled credentials only; no real user touched. Severity high - an attacker who can deliver the token to a victim gains an authenticated session without any interception.

03Four delivery vectors, one confirming probe each

The mechanism that plants the pre-chosen token in the victim's browser changes the delivery step but not the confirming probe. Some vectors require a specific server configuration or a loose cookie-setting boundary; others exploit design patterns that look intentional but were never meant as security controls.

Delivery vectorPreconditionConfirming probe adjustment
URL-to-cookie copyServer reads ?sessionid= from the URL and sets it as the cookie valuePresent token in the URL parameter; confirm the Set-Cookie value matches
Cross-subdomain cookie setAttacker controls a subdomain sharing the same domain cookie scopeSet cookie from the sibling subdomain; confirm it is visible on the login subdomain
HTTP response splittingCRLF injection in a response header allows injecting Set-CookieInject Set-Cookie via CRLF; confirm token accepted at login
Pre-auth session not rotatedServer issues a token before login and never replaces itPresent pre-auth token in Cookie at login; confirm absence of new Set-Cookie

The most common case in modern applications is the last row - a framework that issues a pre-authentication session for CSRF or cart state and never regenerates it on successful login. No cross-subdomain or CRLF precondition is needed; the confirming probe is identical to the one above.

04Severity follows delivery reachability

Token non-rotation alone is the root cause and always warrants a fix. Severity in the risk score is set by whether an attacker can realistically plant the token in the victim's browser. A non-rotation finding on an application with no URL-copy vector and no subdomain the attacker can reach is rated medium - real but harder to exploit. A non-rotation finding where the application copies a URL-supplied session ID into a cookie, accessible over the public internet, is rated high - the attacker only needs to send the victim a crafted link. A critical rating applies when CRLF injection or another direct injection path can set the cookie in the victim's browser without any click, combining two confirmed findings into a single chain.

The fix is always server-side and always the same: call the session regeneration function immediately after a successful authentication event, before issuing any redirect. The new token must be generated from a cryptographically secure source - not derived from the old one and not reused from the URL parameter. Most frameworks expose a one-call fix: session.regenerate() in Express, request.session.cycle() in Rack, SessionFixationProtectionMixin in Django. Not calling it is the entire bug.

2
probe steps to confirm
4
delivery vectors tracked
0
real user sessions accessed

Every finding carries the two-step PoC - the controlled login request and the clean-session retrieval - so the fix is verifiable by the engineer who implements it: run the same two requests, confirm the second returns a 401 or 403, and the session boundary is closed.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →