CWE-384: Session Fixation
Overview
Session Fixation occurs when an application allows an attacker to set or reuse a session identifier for another user, enabling the attacker to hijack the victim's session after authentication. It works because the identifier survives the authentication boundary: the session the victim authenticates into is one the attacker already knows. There are two ways to get that identifier in front of the victim. The attacker can invent a value, which works only where the server creates a session under any identifier it is handed. More often - and this is the case to assume - the attacker visits the site first, is issued a perfectly genuine identifier, and plants that one. The second route defeats any check on whether the server issued the identifier, because it did. Regenerating the identifier at authentication closes both, which is why it is the primary defence rather than one option among several.
Relationship to Other CWEs
MITRE classifies CWE-384 as a Composite: a weakness that arises only when several distinct weaknesses are present at the same time, so removing any one of them eliminates or sharply reduces the risk. Three are recorded as required, and all three have pages here. The composite and its components:
- CWE-384 (this page) - a session identifier established before authentication stays valid after it, so an identifier the attacker planted becomes the victim's authenticated session.
- CWE-346 (Origin Validation Error) - the server acts on a session identifier without establishing that it issued the identifier itself.
- CWE-472 (External Control of Assumed-Immutable Web Parameter) - the identifier is treated as fixed by the application while remaining something a caller can set.
- CWE-441 (Unintended Proxy or Intermediary / 'Confused Deputy') - the application then acts on that identifier with the victim's authority.
CWE-384 is ChildOf CWE-610 (Externally Controlled Reference to a Resource in Another Sphere) in both the Research Concepts and Simplified Mapping views, and CanFollow CWE-340 (Generation of Predictable Numbers or Identifiers) where the identifier is guessable as well as settable. Neither has a page here. MITRE's mapping guidance is Allowed, so a finding can carry this number.
OWASP Classification
A07:2025 - Authentication Failures
Risk
High: An attacker who plants a session identifier is authenticated as the victim the moment the victim logs in, holding whatever access that account has.
Remediation Steps
Core Principle: A session identifier established before authentication must not stay valid after it. Regenerate the identifier whenever a user's authentication or privilege level changes.
Locate the session fixation vulnerability
- Review the flaw details to find where session IDs are not regenerated after authentication
- Check the authentication flow: login handlers, OAuth callbacks, SSO integrations
- Compare the session ID before and after login to see whether it changes
- Identify where session IDs come from: cookies, URL parameters (dangerous), hidden form fields
Regenerate session IDs after authentication (Primary Defense)
- Issue a new session ID at the point credentials are accepted, by calling
session.regenerate(),session_regenerate_id(true), or the framework equivalent - The old ID must stop referring to any session at all, not merely stop being the current one. Check the argument the regeneration API takes: PHP's
session_regenerate_id()defaults todelete_old_session = false, which writes the session's current contents back to the old identifier and leaves it live. What that costs depends on ordering, measured on PHP 8.5.8 - regenerate after writing the authenticated state and the old file keepsuser_id, so the pre-login identifier remains a fully authenticated session until it expires on its own; regenerate before writing it and the old file survives empty, which is harmless for fixation. Passingtruedestroys it either way and does not depend on getting the order right - Copy user data, shopping cart and preferences from the old session to the new one
- Regenerate again when a user's role changes (user → admin)
- Use built-in regeneration rather than hand-rolling it, and check what each method does with the data already in the session. Django's
request.session.cycle_key()keeps it, anddjango.contrib.auth.login()callscycle_key()for you - a finding against a Django application usually means a login path that writes the session directly and bypasses it. Express'sreq.session.regenerate(callback)starts an empty session, so anything worth keeping has to be written again inside the callback, not before the call
Use secure session management practices
- Set
Secure(HTTPS only),HttpOnly(no JavaScript access), and aSameSitevalue chosen per flow as CSRF defense-in-depth.Strictis not simply the stronger setting - it also withholds the cookie from inbound links, SSO redirects and OAuth callbacks, which lands the user signed out with no error anywhere. See CWE-614 - Keep the two session timers separate: an idle timeout that resets on activity (2-5 minutes for high-value applications, 15-30 minutes for lower-risk ones) and an absolute timeout that does not reset (commonly 4-8 hours, sized to a normal working session), after which the user re-authenticates regardless of activity
- Generate session IDs from a cryptographic random source: SecureRandom, os.urandom(), crypto.randomBytes()
- Give each ID at least 128 bits (16 bytes) of randomness
Prevent session ID exposure
- Never accept a session ID from a URL. A GET parameter carrying one ends up in server logs and browser history
- Transmit session IDs only in HTTP-only cookies, not URL parameters or hidden form fields
- When a request presents an ID the server has no record of issuing, discard it and issue a fresh one instead of creating a session under it. This is the permissive default in PHP -
session.use_strict_modeis0unless you set it to1. Be clear about what it buys: it removes the route where an attacker invents an identifier, and it does nothing against one who requests a genuine identifier first and plants that. Measured on PHP 8.5.8 withuse_strict_mode = 1, a planted genuine ID was accepted for the victim's login and replayed afterwards to reach the authenticated session. Regeneration is still the fix - Carry all session communication over HTTPS, with the Secure flag set on cookies
Monitor and audit session activity
- Log session creation, regeneration and termination events, which is also how you confirm regeneration is running on every login
- Alert on suspicious patterns: the same session ID arriving from multiple IP addresses, rapid session creation, session ID reuse
- Tie the session to client properties such as IP address or user agent, and treat a mismatch as a signal to investigate rather than proof of theft - an attacker behind the same NAT gateway or corporate proxy presents the victim's address, a user agent can be copied verbatim, and mobile users change address legitimately mid-session
Test the session fixation fix
- Capture the session ID before and after login: the two differ. An identical ID means regeneration never ran
- Replay the pre-login ID after authentication: the request is treated as unauthenticated. If it still reaches authenticated pages, the old session was superseded rather than destroyed - the most common outcome of a regeneration call that was given the wrong argument
- Continue the post-login session with the new ID: it works, and data written before login (cart, preferences, part-completed forms) is still there. This is the assertion that separates a working fix from one that invalidates everything, and it is the one usually left out
- Submit a session ID the server never issued: the response sets a different ID, and no session exists under the submitted one afterwards
- Log in from a second device: both sessions work independently, and regenerating one does not invalidate the other
- Check that the Secure, HttpOnly and SameSite flags are set
- Re-scan to confirm the finding is resolved
Common Vulnerable Patterns
- Reusing session IDs after authentication
- Accepting session IDs from GET parameters or external sources
Session ID Not Regenerated After Login (Pseudocode)
// VULNERABLE - Session ID Not Regenerated After Login
# Does not regenerate session ID after login
user = authenticate(username, password)
if user:
session['user_id'] = user.id # session_id remains the same!
# Attacker who set the session ID can now hijack authenticated session
Why this is vulnerable: The session ID the victim arrives with is the session ID they end up authenticated under. An attacker who planted that ID, through a URL parameter or cookie injection, holds a live authenticated session the moment the victim logs in.
Secure Patterns
Session Regeneration After Authentication (Pseudocode)
# Regenerate session ID after login
user = authenticate(username, password)
if user:
session.regenerate_id() # Create new session ID
session['user_id'] = user.id
# Old session ID is now invalid, attacker cannot hijack
Why this works:
- A new session ID is issued after successful authentication and the session behind the old one is destroyed, so a pre-set attacker ID stops referring to anything. Regeneration that issues a new ID without deleting the old session leaves the attacker's copy authenticated and fixes nothing
- An identifier the attacker planted, in a URL or in a cookie, is useless once the victim logs in: there is no session under it to hijack
- The Secure, HttpOnly and SameSite cookie flags protect the new identifier once it has been issued
Migration Considerations
Adding session regeneration to the login flow does not by itself log out active users. Changing the session storage format, cookie name, or signing/encryption keys does.
What Breaks
- Active sessions are invalidated if the deployment changes the session store format, cookie names or keys, or deliberately clears sessions. Those users have to log in again
- Shopping carts are lost on e-commerce sites unless cart data is persisted outside the session
- Multi-step forms and wizards reset
- Session-based API authentication is terminated too
Migration Approach
Option 1: Graceful Session Migration (Recommended)
Migrate sessions gradually without logging everyone out:
- Create a new, cryptographically random session identifier
- Copy the session data across to it
- Mark the old session as regenerated, and record where it was regenerated to
- Handle in-flight requests: mark the old ID as replaced and allow only a short, non-privileged redirect or retry path, not continued authenticated use
- Reject or redirect requests that present a replaced pre-authentication ID
- Trigger regeneration immediately after successful authentication
- Carry shopping carts, form data and other session state through the regeneration
Option 2: Big-Bang Session Invalidation (Simple but Disruptive)
Log everyone out at deployment:
- Remove all session data from the session store
- Or, instead of clearing it, set all existing sessions to expire in 5 minutes
- Tell users they need to log in again, and that it is a one-time security change
User Communication Template:
Subject: Security Update - Please Log In Again
We've made a security change that requires everyone to log in again. This is a one-time occurrence and your data is unaffected.
Rollback Procedures
If the deployment causes problems:
- Roll back to the previous version using version control
- Fix data-copy or redirect handling rather than extending authenticated use of replaced session IDs
- Use a feature flag to disable session regeneration while you investigate
- Check logs for session-related errors since the deployment
- Alert if the session error rate exceeds its normal baseline
Testing Recommendations
These cover the deployment itself. The assertions that prove the fix works are in Remediation Steps above.
Pre-Deployment Testing:
- A session created before the deployment either keeps working or fails cleanly at the login page - no partially readable session state
- A request that arrives with a replaced ID is sent to re-authenticate, not served authenticated content. There is no window in which the pre-authentication ID still carries privileges
- Session data written before login is present after regeneration
- Load test: regeneration adds a write and a delete to the session store on every login - confirm the store keeps up at peak login rate
Post-Deployment Monitoring:
- Monitor login/logout rates
- Track session regeneration events
- Alert on unusual authentication error rates
- Monitor support tickets for login issues
- Track session expiry patterns
Key Metrics to Track:
- Active session count
- Session regenerations per hour
- Failed authentication attempts
- Requests still presenting a replaced session ID
- Average session lifetime
- Session error rate
- User login/logout frequency