Session fixation is an attack in which the attacker gets a victim to use a session identifier the attacker already knows, waits for the victim to log in with it, and then uses the same identifier to act as the victim. It differs from session hijacking in the order of events: in hijacking the attacker steals an identifier after the victim has logged in, while in fixation the attacker supplies the identifier before the login and relies on the application keeping it afterwards. The standard defense is to issue a new session identifier whenever someone logs in, so that anything the attacker planted before login stops being valid.
This post explains how the attack works, how it compares with session hijacking and cookie theft, where it still turns up in modern applications, how to defend against it, and how to check your own login for it. It is aimed at engineers who own an application’s sign-in flow.
What is a session fixation attack?
A web application tracks a visitor with a session identifier, usually stored in a cookie. Many applications create that identifier on the first request, before anyone has logged in, so they can remember a shopping cart or a redirect target. If the application keeps the same identifier after login and merely marks it as authenticated, the identifier becomes the key to a logged-in session.
The OWASP description of session fixation lays out the sequence. The attacker obtains a valid identifier, which can be as simple as requesting the login page. The attacker then gets the victim to use it, for example through a crafted link that carries the identifier in the URL or through injected script. A sibling subdomain that can set cookies for the parent domain is another route in some setups. The victim logs in. Because the application did not change the identifier, the attacker, who already holds it, is now logged in as the victim without ever seeing a password.
How is session fixation different from session hijacking?
Both end with the attacker holding a working session, but the attacker’s position is different.
| Session fixation | Session hijacking | |
|---|---|---|
| When the attacker gets the identifier | Before the victim logs in | After the victim has logged in |
| How | Plants it, by link, cookie or injected script | Steals it, by malware, network interception or script injection |
| What it needs from the application | Keeping the same identifier after login | Any way for the identifier or token to leak |
| What fixes it | A new identifier at login | Protecting storage and transport, short lifetimes, binding the session to a device |
Session hijacking through information-stealing malware is the more common story at the moment, and we cover it in our post on session hijacking and infostealers. Cross-site scripting that reads a session cookie is a hijacking technique too, and the HttpOnly cookie flag, which hides a cookie from scripts, limits that route without helping against fixation, because the attacker never needs to read the cookie in the fixation case. Both attacks are routes to account takeover.
Where does session fixation still show up?
Frameworks have generally learned to rotate identifiers at login, so most fixation bugs live in places that bypass the framework:
- Custom session stores. Applications that build their own session table, or use a signed token as a session, and keep the same value across the login boundary.
- Identifiers accepted from the URL or a form field. Older patterns such as a
jsessionidin the path or a hidden field that the server trusts. - Logins that happen on a different system than the session. A single sign-on bridge or an IdP-initiated SAML flow can end with the application establishing a session from an assertion, and the application must still create a fresh session at that point. Our posts on IdP-initiated versus SP-initiated SSO and bridging IdP-initiated SAML to OIDC with Keycloak describe those flows.
- OAuth and OpenID Connect callbacks. After the callback the application exchanges a code and then logs the user in. If it attaches the result to whatever session already exists, it inherits any identifier an attacker planted earlier.
- Cookies shared across subdomains. A cookie set for the parent domain by a less trusted subdomain can plant a value for the main application.
How do you prevent session fixation?
The primary control, in the OWASP Session Management Cheat Sheet, is to renew the session identifier after any change in privilege level, and in particular after a successful login. In practice:
- Rotate the session ID at login. Most frameworks have a call for this (it is often named something like
regenerate,reneworchange_session_id). Call it after authentication succeeds and before you store anything about the user. - Refuse identifiers the server did not create. Do not accept a session ID from the URL or a form field, and discard an identifier the server has no record of instead of adopting it.
- Rotate on other privilege changes too. Step-up authentication, role changes and password changes should also issue a new identifier.
- Set cookie attributes. Use
Secure,HttpOnlyandSameSite, and consider the__Host-cookie name prefix, which stops a subdomain from overwriting the cookie. - Invalidate the old session on logout and on login. Destroy the server-side record, not just the browser cookie.
What about applications that sit behind an identity provider?
With an identity provider in the picture there are two sessions to think about: the one at the provider and the one in your application. Fixation matters most on the application side of the OpenID Connect or SAML callback. Make sure your framework’s OAuth library creates a fresh session when it processes the callback, and keep any “remember where the user was going” state separate from the authenticated session. Session lifetimes configured at the provider also limit how long a stolen or planted session is useful, which our post on Keycloak session timeouts explains.
How can you test your login for session fixation?
- Open the application in a clean browser profile and note every cookie set before login.
- Log in, then compare cookie values. Any session cookie that has the same value before and after login is a finding.
- Copy the pre-login cookie into a second browser profile. After the first profile logs in, reload the second. If it is now logged in, the application is vulnerable.
- Repeat for each way in: password login, social login, SSO callback and any IdP-initiated entry point.
In access logs you can look for the same session identifier appearing both before and after an authentication event, which shows the identifier was not rotated.
Frequently asked questions
What is the difference between session fixation and session hijacking?
In fixation the attacker sets or learns the session identifier before the victim logs in and benefits once the victim authenticates. In hijacking the attacker steals the identifier after login.
Does HTTPS prevent session fixation?
No. HTTPS protects the identifier in transit, but fixation does not depend on intercepting traffic. The attacker plants an identifier through a link, a cookie or a script, and the defense is renewing the identifier at login.
Do JSON Web Tokens avoid session fixation?
Not automatically. A token minted at login is a new value by construction, which helps, but an application that stores state in a pre-login session and merges it into the authenticated one can still be affected.
Is session fixation still on the OWASP Top 10?
Not as its own entry. Weaknesses in session handling fall under the Authentication Failures category of the current OWASP Top 10, and session fixation is catalogued as CWE-384 (Session Fixation).
Does logging out fix a fixed session?
Only if the server destroys the session record. If logout just clears the browser cookie, an attacker who holds the identifier can still use it.