7 min read

Session Fixation

Session fixation attacks force a known session identifier onto a victim. When the victim authenticates with the fixed session, the attacker can use the same session ID to access the authenticated session. This differs from session hijacking — the attacker sets the session before authentication, not after.

Frameworks: WSTG-SESS-03 (also WSTG-SESS-02) · ASVS 5.0 V7.2.4, V7.4.1 · CWE-384 · T1539

The Three Preconditions

Fixation has three preconditions, and it fails if any one is missing. The application must accept a session identifier it did not generate, or let an attacker plant one it will adopt; that identifier must survive authentication; and the attacker must be able to use it afterwards. Most current frameworks rotate at login, which removes the second precondition — that is why the bug is rarer than its reputation, and why proving all three beats finding a payload.

Rotating the identifier at login is the whole fix because it breaks the second precondition outright: whatever the attacker planted becomes a dangling anonymous session. But rotation must be server-side and complete. Issuing a new cookie while leaving the old session record valid is not rotation — the attacker's copy still maps to an authenticated session. Destroy the old record, not just the reference to it.

Attack Flow

  1. 1. Attacker obtains a valid session ID from the target app (just visit the login page)
  2. 2. Attacker crafts a URL or tricks the victim into using that session ID
  3. 3. Victim clicks the link and authenticates with the fixed session
  4. 4. The application does NOT regenerate the session ID after login
  5. 5. Attacker uses the same session ID to access victim's authenticated session

Session Fixation via URL

The attacker-sets-the-ID step is where most tests go wrong. Visiting the login page and reading Set-Cookie gives you an identifier the server already knows, which is only enough if the application also accepts it from a URL or lets you replace it. Keep the value, complete the login in a separate cookie jar, then replay it. If the replay lands on the dashboard, the session survived authentication.

URL-borne identifiers are a fixation vector and a leak of their own: they travel in Referer headers, proxy logs, bookmarks and chat history. If a query parameter or a ;jsessionid= path parameter establishes a session, treat it as two findings — fixation and exposure — and check whether the application rewrites URLs to strip it.

session-fixation-url.sh
# Step 1: Get a session ID from the target:
curl -sI https://target.com/login
# Set-Cookie: PHPSESSID=abc123def456; path=/

# Step 2: Craft URL with the session ID:
https://target.com/login?PHPSESSID=abc123def456

# Or for Java applications:
https://target.com/login;jsessionid=abc123def456

# Step 3: Send this URL to the victim (phishing)
# Victim logs in → session abc123def456 is now authenticated

# Step 4: Use the session:
curl -s https://target.com/dashboard \
  -H 'Cookie: PHPSESSID=abc123def456'
# If authenticated → session fixation confirmed!

Cookie injection needs a delivery primitive you already have: XSS, a controlled subdomain whose cookie is scoped to the parent domain, or a CRLF in a response header. If none of those exist, this path is theoretical and belongs in the report as a chain rather than a standalone vulnerability. Note the subdomain case in particular — a cookie set with Domain=.target.com from sub.target.com is sent to www.target.com, and the application has no way to tell who set it.

session-fixation-cookie.html
# If the target has an XSS vulnerability or subdomain cookie scope:

# Via XSS:
<script>document.cookie='PHPSESSID=ATTACKER_SESSION; path=/; domain=.target.com';</script>

# Via subdomain (if attacker controls sub.target.com):
# Set a cookie scoped to .target.com from sub.target.com
# This cookie will be sent to www.target.com

# Via CRLF injection in HTTP headers:
https://target.com/redirect?url=http://target.com%0d%0aSet-Cookie:PHPSESSID=ATTACKER_SESSION

Testing Procedure

The test is a comparison between identifiers, not between cookie names. Record Set-Cookie before login, authenticate with that cookie, then record it again. A new value — with the old one rejected — is safe; the same value is a finding. Check the negative case explicitly: request the old identifier against an authenticated route and confirm it fails. A framework that reuses the cookie name while creating a new server-side session passes the first check and fails a naive one.

session-fixation-test.sh
# Step 1: Note the session ID before login:
curl -sI https://target.com/login | grep -i set-cookie
# PHPSESSID=SESSION_BEFORE_LOGIN

# Step 2: Login with this session:
curl -s -X POST https://target.com/login \
  -H 'Cookie: PHPSESSID=SESSION_BEFORE_LOGIN' \
  -d 'username=testuser&password=testpass' \
  -D - -o /dev/null | grep -i set-cookie

# Step 3: Check if the session ID CHANGED after login:
# If Set-Cookie contains a NEW session ID → SAFE (session regenerated)
# If NO new Set-Cookie → VULNERABLE (session not regenerated)

# Step 4: Verify the old session is still valid:
curl -s https://target.com/dashboard \
  -H 'Cookie: PHPSESSID=SESSION_BEFORE_LOGIN'
# If authenticated → session fixation confirmed!

# Also test: does the app accept arbitrary session IDs?
curl -s https://target.com/login \
  -H 'Cookie: PHPSESSID=ATTACKER_CHOSEN_VALUE_12345'
# If the server adopts this value → fixation possible

Testing Checklist

  • ☐ 1. Note the session ID before authentication
  • ☐ 2. Authenticate and check if the session ID changes
  • ☐ 3. Verify the old session ID is invalidated
  • ☐ 4. Test if the app accepts session IDs from URL parameters
  • ☐ 5. Test if the app accepts arbitrary/attacker-chosen session IDs
  • ☐ 6. Test session regeneration at privilege level changes (admin access, profile changes)

Evidence Collection

Before Login: HTTP response showing pre-auth session ID

After Login: HTTP response showing same session ID persists

Verification: Request using old session accessing authenticated content

Set-Cookie Delta: The two Set-Cookie headers side by side (before and after authentication), plus the authenticated response both identifiers return

ID Adoption Proof: Response showing the server accepted an identifier it never issued — for example an arbitrary cookie value that appears in the next Set-Cookie or in a subsequent request

CVSS Range: 6.5–8.1 depending on attack complexity and impact

Remediation

  • • Regenerate session on login: Always issue a new session ID after successful authentication.
  • • Reject URL-based sessions: Do not accept session IDs from query parameters — use cookies only.
  • • Reject unknown sessions: Only accept session IDs that the server has previously issued.
  • • Regenerate on privilege change: Issue new sessions on role changes, password changes, and MFA completion.
  • • Destroy the old session record: Rotating the cookie is not enough. Invalidate the previous server-side session so a copy of the identifier cannot be replayed.
  • • Rotate at every authentication event: The pattern that survives review is: create a new session, migrate only what the user needs, delete the old one, and set the new cookie with HttpOnly, Secure and SameSite.

False Positive Identification

  • Session ID preserved but session regenerated: Some frameworks reuse the cookie name but create a new server-side session — verify the attacker's pre-set session ID actually grants access post-login.
  • Cookie accepted but not bound: Setting a session cookie that the server doesn't recognize will just create a new session — confirm the server binds to the attacker-supplied ID.
  • Requires existing XSS or network position: Session fixation typically requires another vulnerability to set the cookie — document the prerequisite attack chain in your report.
  • Same ID, anonymous session: An identifier can survive login and still be useless if the authenticated state lives in a second cookie or a token issued at login. Check that the old ID alone reaches authenticated content.
  • Rotation without destruction: A new cookie is not the same as a rotated session. If the old value still loads the dashboard, the fix is incomplete — report that precisely rather than calling the control effective.

Session Management Practice

Attack session lifecycle handling on a deliberately vulnerable application.

OWASP WebGoat — Session Management Flaws Custom Lab medium
Session fixationSession puzzlingCookie scoping