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. Attacker obtains a valid session ID from the target app (just visit the login page)
- 2. Attacker crafts a URL or tricks the victim into using that session ID
- 3. Victim clicks the link and authenticates with the fixed session
- 4. The application does NOT regenerate the session ID after login
- 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.
# 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!Session Fixation via Cookie Injection
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.
# 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_SESSIONTesting 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.
# 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 possibleTesting 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,SecureandSameSite.
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.