Join our Newsletter — 33% off our NHI Course
Home› Guides› Session Fixation Guide: Preventing Session Fixation in Web…
Guide Attack Techniques & Malware

Session Fixation Guide: Preventing Session Fixation in Web Apps

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 26 October 2026 12 min read
On this page

Session fixation lets an attacker inherit a user's logged-in session because the application kept using a session identifier that existed, and could be known to someone else, before the user authenticated. Session fixation is a session management weakness, catalogued as CWE-384 and CAPEC-61, in which the session identifier is not replaced when the user's privilege level changes. Unlike cookie theft, the attacker does not need to steal anything after login: the identifier was known in advance. This guide explains the weakness at concept level, why it still appears in modern frameworks, how to prevent it with framework settings and cookie design, and how to test and detect it. It is written for developers, application security engineers and reviewers who build or assess web applications with server-side sessions.

Key takeaways

  • One control fixes the core flaw: issue a new session identifier on every authentication and privilege change, and terminate the old one. OWASP ASVS 5.0 requirement V7.2.4 makes this a Level 1 requirement.
  • Strict session handling closes the door: never accept a session identifier the server did not generate. NIST SP 800-53 SC-23(3) calls for recognising only system-generated session identifiers.
  • Framework defaults are not uniform: Django's login() rotates the key and Spring Security changes the ID by default, but PHP ships with session.use_strict_mode off and custom session handlers can silently disable it.
  • Cookie scope is part of the defence: the __Host- prefix and an unset Domain attribute stop sibling subdomains from planting a session cookie on your host.
  • It is cheap to test: a single check, comparing the session identifier before and after login, belongs in unit tests, code review and every DAST profile.

What it is

A web application that uses server-side sessions hands the browser a session identifier, usually in a cookie, and looks up the user's state on each request. Many applications create that session for anonymous visitors, for a shopping basket, a CSRF token or a language preference, long before anyone logs in. Session fixation occurs when the user then authenticates and the application simply marks the existing session as authenticated instead of issuing a fresh identifier.

MITRE's CWE-384 entry describes the weakness as authenticating a user, or otherwise establishing a new session, without invalidating any existing session identifier, which gives an attacker the opportunity to steal authenticated sessions. CWE lists three conditions that commonly produce it: the application authenticates without first invalidating the existing session; an attacker can force a known identifier onto a user; or the application or container uses predictable identifiers. Its listed consequence is the ability to gain privileges or assume an identity.

Two terms from the OWASP Session Management Cheat Sheet help frame the root cause:

  • Permissive session management: the server accepts any identifier a client presents and creates a session for it. OWASP notes that PHP defaults to this mode.
  • Strict session management: the server accepts only identifiers it generated itself. An unknown identifier is discarded, a new one is issued and the event is logged as suspicious.

Strict mode removes one way of planting an identifier. Rotation on login removes the value of any identifier that was planted by other means. A secure application needs both.

How it works

This section explains the mechanism at the level needed to design and review defences. It deliberately contains no attack procedure.

Session fixation needs three conditions to line up. Remove any one and the attack fails, which is why the controls in the table below are layered.

ConditionWhat has to be trueControl that breaks it
A known identifier existsThe attacker can obtain a valid pre-login identifier from the application, or the server accepts an arbitrary one (permissive mode)Strict session handling; SC-23(3) system-generated IDs; at least 128 bits from a CSPRNG (ASVS V7.2.3)
The identifier reaches the victim's browserThe identifier can be carried in a URL, set by a related subdomain through a broad Domain cookie, injected over plain HTTP, or written by a script injection or response splitting flawCookie-only transport (ASVS V14.2.1); __Host- prefix with no Domain (ASVS V3.3.3); Secure attribute and HSTS; XSS and header injection prevention
The identifier survives authenticationThe application elevates the existing session on login instead of replacing itNew identifier on authentication and re-authentication, old one terminated (ASVS V7.2.4)

Delivery channels in brief

  • URL-borne identifiers: frameworks that support session IDs in query strings or URL rewriting (PHP's session.use_trans_sid, legacy Java jsessionid rewriting) let an identifier travel in a link. PHP's documentation warns that URL-based session management carries extra risk because URLs are shared and bookmarked.
  • Cookie injection from a related host: Django's session documentation notes that subdomains within a site can set cookies for the whole domain, which makes session fixation possible if cookies are permitted from subdomains not controlled by trusted users. Shared hosting, user content domains and forgotten marketing subdomains are typical sources.
  • Injection through another flaw: OWASP notes that cross-site scripting and HTTP response splitting can be used to set a session cookie, so fixation often appears as a second-stage effect of those bugs.
  • Multiple session cookies: where an application splits session state across several cookies, OWASP warns that an unprotected pre-login cookie can be used to reach the authenticated session unless every cookie and the relationship between them is verified.

Why it matters

The result of a successful fixation is full use of the victim's authenticated session, with whatever the user can do: read data, change settings, approve payments or, for an administrator, change other users. Because the user authenticates normally, multi-factor authentication does not help: the attacker never presents credentials, only the identifier the user has just elevated. This is the same reason session theft in general is attractive, covered in the Session Hijacking and Cookie Theft Guide.

The weakness also has a quieter variant: the attacker fixes a session that they themselves log into, and the victim then enters sensitive data into the attacker's account. Django's documentation gives exactly this example for a subdomain that sets a domain-wide cookie. The impact is data disclosure rather than account takeover, but it is still a breach.

Fixation keeps appearing for practical reasons:

  • Custom login code: teams that bypass the framework's login helper, for example to add an SSO callback or a passwordless flow, often forget the rotation the helper performed.
  • Step-up and role switches: applications rotate on first login but not on re-authentication, MFA step-up, "switch organisation" or administrator impersonation, all of which are privilege changes.
  • Configuration drift: PHP's documentation states that if a custom handler registered with session_set_save_handler() does not implement validateId(), strict mode is effectively disabled regardless of the directive.
  • Federated sessions: an identity provider session and several relying party sessions each need their own rotation. ASVS V7.1.3 asks for federated session lifetimes and termination to be documented and coordinated.

Prevention by framework

Prefer the framework's built-in session management to anything custom. The table summarises the main controls in common stacks, taken from each project's documentation.

StackBuilt-in fixation controlWhat to check
Djangodjango.contrib.auth.login() calls request.session.cycle_key(), which creates a new session key while retaining data, to mitigate fixation. update_session_auth_hash() rotates the key on password changeCustom authentication views still call login(); SESSION_COOKIE_DOMAIN is not set to a parent domain; SESSION_COOKIE_SECURE enabled. See the Django Security Guide
Spring SecuritysessionFixation() strategies: changeSessionId (default on Servlet 3.1 and later), newSession, migrateSession (default on Servlet 3.0 and older); none disables protection and is documented as not recommendedNo configuration sets sessionFixation().none(); a SessionFixationProtectionEvent is published on login. See the Java and Spring Security Guide
PHP sessionssession.use_strict_mode (default 0; PHP's manual calls enabling it mandatory for general session security); session_regenerate_id() on loginuse_strict_mode=1, use_only_cookies=1, use_trans_sid=0; custom handlers implement validateId(); regenerate on login and privilege change. See the PHP and WordPress Security Guide
Any stackSecondary random cookie bound to the session, as CWE-384 suggests for platforms that cannot regenerate identifiersMismatch between cookie and stored value forces logout and re-login

Implementation sequence

  1. Inventory privilege transitions: list every point where a session's authority changes: login, logout, SSO callback, MFA step-up, password or factor change, role or tenant switch, impersonation start and end.
  2. Rotate at each one: call the framework's rotation function and destroy or expire the old session server-side. The OWASP cheat sheet also recommends rotation on password, permission and role changes.
  3. Turn on strict handling: reject identifiers the server did not issue, issue a fresh one and log the event.
  4. Harden the cookie: name it with the __Host- prefix, set Secure, HttpOnly and an explicit SameSite value, and leave Domain unset. The cookie prefixes are defined in the IETF rfc6265bis draft, which requires __Host- cookies to be Secure, to have Path=/ and to carry no Domain attribute.
  5. Remove URL session support: disable URL rewriting and reject identifiers from query strings.
  6. Test in a staging ring first: rotation can break concurrent requests and single-page apps that cache the old cookie. PHP's manual warns that deleting the old session immediately can lose sessions on unstable mobile networks and recommends marking the old session with a destroy timestamp instead. Roll back by reverting the configuration flag, not by removing rotation from code.

Detection and testing

  • Unit and integration tests: assert that the session identifier after login differs from the one before, and that the old identifier no longer returns authenticated content. Repeat for every transition in your inventory. See the Secure Code Review Guide.
  • Verification standard: ASVS V7.2.4 (rotation), V7.2.3 (entropy), V3.3.1 and V3.3.3 (cookie prefixes), V3.3.4 (HttpOnly) and V7.4.1 (termination) give a ready test set. See the OWASP ASVS Guide.
  • Runtime signals: log session creation, renewal and destruction, and alert on requests that present identifiers the server never issued. OWASP advises logging a salted hash of the identifier, never the raw value.
  • Anomaly hints: a change of IP address or user agent within a session can flag misuse, but OWASP treats these as detection aids only, because NAT and proxies make them unreliable as controls.
MetricTarget
Privilege transitions with an automated rotation test100% of the inventory
Applications with strict session handling enabled100% of internet-facing apps
Session cookies using the __Host- prefixAll cookies not intentionally shared across hosts
Unrecognised session identifier events per weekBaselined, with spikes investigated
DAST and pen test findings for CWE-384Zero open beyond SLA

Common misconceptions

  • "HTTPS prevents it." TLS protects the identifier in transit, but fixation relies on a value the attacker already knows. OWASP notes that rotation is needed whether the site uses HTTP or HTTPS.
  • "HttpOnly prevents it." HttpOnly stops scripts reading a cookie; it does not stop a cookie being set by a sibling domain, a URL parameter or a response header injection.
  • "MFA makes it irrelevant." MFA protects the login step. If the pre-login identifier survives, the attacker inherits the session after MFA succeeds.
  • "Our framework handles it." Only if your code uses the framework's login path. Custom SSO callbacks and token-to-session bridges often bypass it.
  • "Short timeouts are enough." Timeouts limit the window, and NIST SP 800-63B rev. 4 sets overall and inactivity limits per assurance level (at AAL2, no more than 24 hours overall and 1 hour inactivity, as SHOULD requirements), but the attacker acts within that window.
  • "Token-based APIs are immune." Pure bearer token APIs do not have the classic flaw, but any cookie session layered on top of them, such as a backend-for-frontend, does.
ConceptRelationship to fixationReference
Session hijacking and cookie theftAttacker obtains an identifier after login; fixation supplies it before loginATT&CK T1539 Steal Web Session Cookie; T1550.004 Web Session Cookie
Browser session hijackingAbuse of an existing browser session from the endpointATT&CK T1185
Session replay and predictionReuse or guessing of identifiers; both need strong, single-use identifiersCAPEC-60, CAPEC-59
Cross-site request forgeryAbuses an existing session rather than planting one; SameSite helps bothSee the CSRF Prevention Guide
Cross-site scriptingA common delivery channel for a fixed cookieSee the Cross-Site Scripting (XSS) Prevention Guide
Session lifecycle controlsTermination, re-authentication and unique identifiersNIST SP 800-53 AC-12, IA-11, SC-23, SC-23(1), SC-23(3); NIST CSF 2.0 PR.AA-03

For token lifetimes, refresh and binding beyond cookies, see the Token and Session Security Guide.

Practitioner checklist

  • Inventory every login, step-up, role switch, impersonation and password or factor change, and rotate the session identifier at each.
  • Terminate the old session server-side after rotation, with a short grace period only where concurrency requires it.
  • Enable strict session handling (PHP session.use_strict_mode=1) and confirm custom handlers implement identifier validation.
  • Confirm no Spring configuration sets sessionFixation().none() and that custom Django views call login().
  • Use the __Host- prefix with Secure, HttpOnly and an explicit SameSite value; never set Domain to a parent domain.
  • Disable URL-based session identifiers in every framework and proxy.
  • Add a before-and-after login identifier test to CI and to DAST profiles, mapped to ASVS V7.2.4.
  • Log session lifecycle events with hashed identifiers and alert on unrecognised identifiers.

Standards and references

Related NHI Mgmt Group resources: Session Hijacking and Cookie Theft Guide · Token and Session Security Guide · CSRF Prevention Guide · OWASP ASVS Guide · Application Security Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 26 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org