Session Fixation Explained: Forcing a Known Session ID
How session fixation works: an attacker plants a session ID, the victim logs in with it, and the attacker inherits the session. Plus the fix.
Most session attacks steal a token that already belongs to the victim. Session fixation runs the idea in reverse. The attacker chooses a session identifier first, gets the victim to log in while carrying it, and then uses that same identifier to share the victim's authenticated session. The victim does the authentication; the attacker keeps the result.
It is a subtle bug because nothing looks stolen. The victim types their real password into the real site over a real connection. The flaw is that the application trusts a session ID it should have thrown away.
The one condition that makes it possible
Session fixation depends on a single application behaviour: the session ID stays the same across the login boundary. If the token you carry as an anonymous visitor is the same token you carry after authenticating, then anyone who knew the pre-login token now holds a valid post-login session.
A correctly built application issues a brand new session ID the moment a user authenticates. The pre-login token is discarded and can never be upgraded to an authenticated one. When that regeneration is missing, fixation becomes possible.
How the attack runs
The attack has three moves.
- Obtain a valid session ID. The attacker visits the target application and receives a session token like any anonymous visitor. It is not authenticated yet, but the application already recognises it.
- Plant that token in the victim's browser. The attacker forces the victim to adopt the same session ID before they log in.
- Wait for the victim to authenticate. The victim logs in normally. If the application reuses the pre-login ID, the token the attacker planted is now attached to the victim's authenticated session. The attacker presents the same token and is logged in as the victim.
The interesting step is planting the token. Attackers have a few routes.
URL parameters. Some applications accept the session ID from the query string, for example a link ending in ;jsessionid= or a ?sid= parameter. The attacker emails or posts a link with a session ID they chose. Anyone who clicks it adopts that ID. This is the classic and easiest fixation vector, which is why accepting session IDs from URLs is discouraged so strongly.
Cross-site scripting. If the site has an XSS flaw, the attacker's script can write the cookie directly, setting the victim's session ID to a known value.
A crafted cookie. In some setups an attacker can set a cookie for the target domain from a related subdomain or through a meta tag, fixing the value before login.
Fixation versus hijacking
The two attacks are close cousins and share most of their defenses, so it helps to hold the difference clearly.
| Session hijacking | Session fixation | |
|---|---|---|
| What the attacker does | Steals a token the victim already holds | Plants a token the victim then authenticates |
| When it happens | After the victim is logged in | Before the victim logs in |
| How the token is obtained | XSS, interception, malware | URL parameter, XSS write, crafted cookie |
| Core fix | Protect the token from theft | Regenerate the token at login |
The full mechanics of the theft-based version are covered in Session Hijacking Explained. The good news is that the one control that stops fixation, regeneration at login, also limits how long any hijacked token stays valid, so the effort pays off twice.
Session fixation leaves no visible trace for the user. They visit the genuine site, log in with their genuine password, and everything works. There is no phishing page and no credential theft to notice. The compromise lives entirely in the server's decision to keep an old session ID, which is why the fix has to be on the server, not in user awareness.
Why input filtering does not help here
There is no malicious payload to filter. The session ID the attacker plants is a perfectly valid token the application itself issued. Blocking odd-looking values does nothing, because the value is legitimate. The only thing that neutralises it is invalidating that token at the moment authentication succeeds, so its pre-login identity cannot survive into an authenticated one.
This is also why fixation belongs to the broader family of identification and authentication failures in the OWASP Top 10. The weakness is in how identity is bound to a session, not in any single tainted input.
Where JWT-based sessions fit
Applications that carry session state in a JSON Web Token face a related version of the problem. The token is issued at login, so the natural design already mints a fresh credential at authentication, which sidesteps classic fixation. The risks shift to how that token is signed, validated, and expired, and to whether a pre-login token can be reused. See JWT Attacks Explained for those specifics.
A worked example, step by step
Following the attack through once, conceptually and without working exploit code, makes the abstract three-move sequence concrete and shows exactly where the fix lands.
An application accepts a session identifier from the URL, using a parameter like ?sid=. This is the setup that makes fixation easy. The attacker visits the site as an anonymous user and receives a session token, or simply chooses a valid-looking identifier the application will accept. Call that value the fixed identifier. It is not authenticated to anyone yet, but the application already recognizes it as a session.
The attacker now crafts a link to the real site that carries the fixed identifier in the URL, and delivers it to the victim through email, a forum post, or a message. The link points at the genuine application, so nothing about it looks suspicious to someone who trusts the domain. When the victim clicks, their browser adopts the fixed identifier as their session for that site.
The victim then logs in normally. They see the real login page on the real domain, type their real password, and authenticate successfully. Here is the flaw: if the application keeps the same session identifier across the login boundary, the fixed identifier the attacker chose is now attached to the victim's authenticated session. The victim's successful login upgraded the attacker's known token.
The attacker, who has known the fixed identifier all along, now presents it to the application. The server looks it up, finds an authenticated session belonging to the victim, and serves the victim's account. The attacker never stole anything and never phished a password. They simply knew, in advance, the identifier that the victim's own login would authenticate.
The single broken assumption in the whole sequence is that the post-login session keeps the pre-login identifier. Regenerating the session identifier at the moment authentication succeeds breaks precisely that assumption. The identifier the attacker fixed is discarded the instant the victim logs in, the victim receives a fresh identifier the attacker never saw, and the attacker's known token points at nothing. One action, placed at the login boundary, removes the entire attack.
Why the fix has to live on the server
It is tempting to look for a client-side or network defense, and it helps to see why those do not solve fixation.
Input filtering does not apply, because the planted token is a valid identifier the application itself issued. There is no malicious string to detect. Blocking odd input does nothing when the input is legitimate.
Transport encryption does not apply, because the victim's login genuinely happens over an encrypted connection to the real site. Nothing is intercepted. The token was planted before the secure login, not stolen during it.
User awareness does not apply, because the victim sees nothing wrong. They visit the genuine domain, see the genuine login page, and enter their genuine password. There is no phishing page to recognize and no warning sign to heed.
That leaves the server. The decision to keep or discard the pre-login identifier is made server-side, at the moment authentication succeeds, and that is the only place the attack can be neutralized. This is why every serious remediation for fixation is a server behavior, and why session regeneration at login is described as the complete fix rather than one control among many.
How fixation fits the OWASP picture
Session fixation belongs to the broader family of identification and authentication failures in the OWASP Top 10, catalogued under A07 Identification and Authentication Failures. The reason it sits there rather than among injection or access-control flaws is that the weakness is in how identity is bound to a session. The application authenticates the user correctly and then binds that authenticated identity to a session identifier that an outsider already knew. The bug is in the binding step, not in the credential check.
Seeing it this way connects fixation to its neighbors. Weak session identifiers, missing regeneration, session tokens that never expire, and identifiers exposed in URLs are all session-management weaknesses in the same category. An application that gets one of them wrong often gets others wrong too, because they share a root cause: session identity treated casually rather than as a sensitive credential with a defined lifecycle.
How the weakness has changed over time
Session fixation was far more common when applications routinely carried the session identifier in the URL. In that era, passing the identifier through a query string or a path segment was a normal pattern, sometimes used to support clients that did not accept cookies, and it handed attackers the simplest possible planting vector. Fixation was easy because the token was easy to place.
Two shifts reduced its prevalence. Frameworks moved toward cookie-based sessions by default and away from URL-borne identifiers, which removed the easy planting route from most applications. And framework authentication increasingly regenerated the session at login as standard behavior, which closed the root cause for anyone using the built-in login flow. The combined effect is that a developer using a mainstream framework normally gets protection without thinking about it.
The weakness did not disappear. It moved to the places where the defaults are bypassed: hand-rolled authentication, legacy systems that still accept identifiers from URLs, and applications that reuse a session across login for convenience or to preserve pre-login state carelessly. The pattern mirrors many web weaknesses. The framework default became safe, so the remaining risk concentrates in custom code and older systems where the safe default was never adopted. This is why a security review still checks for it directly rather than assuming a modern stack rules it out.
Common mistakes and misconceptions
Several misunderstandings leave fixation unaddressed even in applications that pass a casual review.
The first is assuming that setting a new cookie value at login is the same as regenerating the session. Some code replaces the cookie the browser holds without actually invalidating the old session identifier on the server. If the pre-login identifier still maps to a live session server-side, the attack survives. Regeneration has to invalidate the old server-side session, not merely overwrite the client's cookie.
The second is believing that using cookies instead of URLs makes an application immune. Refusing session identifiers from URLs removes the easiest planting vector, which is genuinely valuable, but an attacker with an XSS bug or the ability to set a cookie from a related subdomain can still plant a fixed value. URL hardening reduces the attack surface. Regeneration at login is what actually closes the attack.
The third is treating fixation as a rare or theoretical bug. It is a design default in any framework configured to reuse the session across login, which happens more often than teams expect when authentication is built by hand rather than using a framework's session manager. The bug is common precisely because doing nothing special produces it.
The fourth is regenerating at login but not at privilege escalation. An application that mints a fresh session at login but keeps it unchanged when a user steps into an administrative area leaves a smaller version of the same gap. A fixed low-privilege session can ride an escalation into a higher-privilege one if the identifier is not rotated at that boundary too.
How to detect session fixation weakness
Fixation is best eliminated by design, and it is also testable, which matters because the bug is invisible to users and leaves little runtime trace. The check is straightforward in concept.
- Compare the identifier across the login boundary. Record the session identifier the application issues before authentication, log in, and check whether the identifier is the same afterward. If it is unchanged, the application is vulnerable to fixation. A correct application issues a new identifier at login.
- Confirm the old identifier is dead. After login, present the pre-login identifier again and confirm it no longer maps to any session. If the old identifier still resolves to a session, regeneration is incomplete even if a new one was also issued.
- Test whether identifiers are accepted from the URL. Supply a session identifier in a query string or path parameter and see whether the application honors it. Acceptance from the URL is the easiest planting vector and should be rejected.
- Check privilege boundaries. Repeat the before-and-after comparison around a privilege step-up, such as entering an admin area, to confirm the identifier rotates there too.
These checks are cheap to run and belong in the authentication test suite, because a regression that quietly removes session regeneration reintroduces the vulnerability without any visible symptom. Runtime detection is weak for fixation, so testing the behavior directly is the reliable path.
Frequently asked questions
Is session fixation still relevant with modern frameworks?
Yes, though modern frameworks make it easy to avoid. Most mainstream frameworks regenerate the session at login when their built-in authentication is used, which closes the attack by default. The risk reappears when developers build authentication by hand, reuse a session across login for convenience, or disable regeneration without understanding why it exists. The vulnerability is a configuration and design issue that frameworks help with but do not guarantee away.
How is session fixation different from session hijacking?
In fixation, the attacker plants a session identifier they already know and waits for the victim to authenticate it, so the attack happens before login. In hijacking, the attacker steals a token the victim already holds and replays it, which happens after login. They are mirror images that share most defenses, and regenerating the identifier at login helps against both. The theft-based version is covered in Session Hijacking Explained.
Does regenerating the session identifier have any downside?
Very little. It costs almost nothing at runtime and is a single call in most frameworks. The only care needed is to preserve any legitimate pre-login state the application wants to carry forward, such as a shopping cart, by migrating that data to the new session rather than losing it. The identifier itself must change and the old one must be invalidated, but associated data can be moved across deliberately.
Can SameSite cookies alone prevent fixation?
No. SameSite reduces some cross-site ways an attacker might plant a cookie, and it is worth setting, but it does not address the root cause, which is the server reusing the pre-login identifier after authentication. An attacker who plants the identifier through a URL parameter or an XSS write can still succeed if regeneration is missing. SameSite is supporting hygiene, and regeneration at login is the actual fix.
Why does accepting session IDs from the URL make fixation worse?
Because it gives the attacker the simplest possible planting method. If the application reads the identifier from a query string, the attacker only needs to send the victim a link carrying a chosen identifier, and clicking it adopts that value. No XSS bug and no cookie manipulation is required. Restricting session identifiers to cookies removes this easy vector and forces an attacker to find a harder way to plant a value.
Does a JWT-based session avoid fixation?
Largely, because a JWT is issued at login, so the natural design already mints a fresh credential at authentication and there is no pre-login token to carry over. The risk shifts to how the token is signed, validated, and expired, and to whether a pre-login token can somehow be reused. A design that issues an unauthenticated token before login and then simply marks it authenticated afterward would reintroduce the same flaw, so the protective property comes from minting a genuinely new token at authentication rather than from the format itself. Those concerns are covered in JWT Attacks Explained.
How to defend against session fixation
The primary control is short and non-negotiable; the rest are supporting hygiene.
- Regenerate the session ID on every login. The instant authentication succeeds, issue a new session identifier and invalidate the old one. This single step ends session fixation. Most frameworks expose a one-call method for it, such as regenerating or migrating the session.
- Regenerate again on privilege change. Any step up in privilege, such as entering an admin area or re-authenticating for a sensitive action, should also rotate the token, so a fixed low-privilege session cannot ride into a higher one.
- Never accept session IDs from the URL. Configure the framework to use cookies only and reject session identifiers supplied in query strings or path parameters. This removes the easiest planting vector.
- Set HttpOnly, Secure, and SameSite on session cookies. These block the XSS-write and cross-site paths an attacker uses to fix a cookie, and they harden the session against theft at the same time.
- Bind the session to the login and expire it aggressively. Tie the authenticated session to the moment of login, and apply idle and absolute timeouts so any planted token has a short life even if regeneration were somehow missed.
- Invalidate on logout. Destroy the session server-side at logout rather than only clearing the cookie, so a known token cannot be resurrected.
Every mainstream web framework provides a way to regenerate the session ID after authentication. Calling it in your login handler closes session fixation completely and costs almost nothing. If you build authentication by hand, wire that regeneration in first, before anything else, and confirm the pre-login token is genuinely invalidated rather than merely replaced in the cookie.
Session fixation is one of the tidier bugs to eliminate, because a single rule removes it: the session a user carries after logging in must never be the session they carried before. Rotate the identifier at authentication and at every privilege boundary, refuse session IDs from URLs, and lock down the session cookie, and the attacker is left holding a token that stops meaning anything the moment the victim signs in.
Authentication and session flaws remain among the most reliably exploited weaknesses on the web. Our Exploit Intelligence dashboard tracks how fast identification and authentication failures move from disclosure to active abuse, and the pattern is consistent enough that a missing session regeneration is worth fixing before it ever reaches a threat model.
Related guides
Sources & further reading
Related guides
- JWT Attacks: alg=none, Algorithm Confusion & Weak Secrets
How JSON Web Token attacks work: the alg=none trick, RS256-to-HS256 algorithm confusion, brute-forcing weak secrets, and how to defend.
- LDAP Injection: Filter Manipulation & Auth Bypass
How LDAP injection lets attackers rewrite directory search filters to bypass logins and read data, why it happens, and how to bind and query safely.
- OAuth Attacks: redirect_uri Manipulation, Codes & CSRF
How OAuth 2.0 attacks work: redirect_uri manipulation, stolen authorization codes, CSRF on the login flow, and how PKCE defends it.