Session fixation
CWE-384OWASP A07:2021Updated August 31, 20266 min read
Session fixation is a vulnerability where an attacker fixes a victim's session id in advance and the application fails to replace that id at login. The attacker's copy of the id then belongs to an authenticated session, so they ride along on the account. The structural fix is to regenerate the session id on every privilege change and destroy the old session immediately.
In many applications, logging in changes exactly one thing: the contents of the session. The long string in the cookie stays exactly as it was, while its meaning shifts from “anonymous visitor” to “authenticated user”. Anyone who already knew that string before you logged in now holds your account. That is session fixation, and the fix is usually a single line of code.
What is session fixation?
Session fixation is a vulnerability in which an attacker determines in advance which session identifier a victim will use, and the application then fails to replace it at login. The session id, short for session identifier, is the only thing the server still needs in order to know who you are. If it is not replaced at the moment the session changes value, the attacker is carried along with it: their copy of the id suddenly belongs to an authenticated account.
An everyday comparison: a theatre cloakroom. Normally you get a fresh ticket when you check your coat. Now imagine someone at the entrance presses their own ticket into your hand, keeping a duplicate in their pocket, and the cloakroom simply accepts that number instead of issuing one. Your coat now hangs on a peg whose number two people know, and whoever turns up first walks off with it.
The difference from session hijacking lies in the ordering. In hijacking, the attacker obtains a session that is already authenticated, for example by stealing a cookie. In fixation, they know the id before anyone logs in and let the victim do the authentication work. Nothing needs intercepting afterwards, which makes the attack quiet and hard to spot in logs.
There are several ways to plant such an id. An application that accepts a session id from the URL will adopt whatever value an attacker puts in a link. A cookie can also be set from a subdomain the attacker controls, since cookies are not isolated per subdomain the way an origin is. And any cross-site scripting anywhere on the domain is enough to write the cookie directly.
How does a session fixation attack work?
Take a login screen that starts the session, checks the password, and then stores the user in the session. The developer considers the job done, but never replaces the identifier itself.
Vulnerable:
<?php
session_start();
$user = find_user($_POST['email']);
if ($user && password_verify($_POST['password'], $user['hash'])) {
// The session id is untouched, only the contents change
$_SESSION['user_id'] = $user['id'];
$_SESSION['is_admin'] = $user['is_admin'];
header('Location: /account');
exit;
}
The attacker starts by choosing a value and making sure the victim’s browser sends it. The request that lands the victim on the login screen then looks like this:
GET /login HTTP/1.1
Host: app.example.com
Cookie: PHPSESSID=b7f1c0a94e2d4c8f9a1e6b3d0c5f8a27
Without a strict check, the server creates a new session under the supplied name instead of issuing an identifier of its own. The victim enters their password, the check succeeds, and $_SESSION['user_id'] is populated. The id in the cookie, however, is still b7f1c0a94e2d4c8f9a1e6b3d0c5f8a27, and the attacker knew it all along. They set the same cookie in their own browser, load /account, and are inside without ever seeing a password.
The fix is to replace the identifier at the moment the session changes meaning. In PHP, session_regenerate_id(true) does exactly that: a new id is issued and the old session is deleted. On top of that, the session.use_strict_mode setting rejects any id the server did not issue itself, so planting a value fails on the very first request.
Secure:
<?php
// php.ini: session.use_strict_mode = 1 and session.use_only_cookies = 1
session_start();
$user = find_user($_POST['email']);
if ($user && password_verify($_POST['password'], $user['hash'])) {
// New id, old session destroyed straight away
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['is_admin'] = $user['is_admin'];
header('Location: /account');
exit;
}
The attacker is now left holding an id that points at nothing once the victim logs in. Their preparation is also useless for a second attempt, because strict mode discards any value they invent on arrival.
session_regenerate_id with true. Without that argument the user does receive a new id, but the old session survives server-side, including the user data that was just written into it. The attacker can simply carry on with the id they planted, and the vulnerability is merely better hidden.What is the impact of session fixation?
A successful session fixation gives an attacker a full account takeover with exactly the victim’s privileges. For an ordinary user that means access to personal data, orders or case files, plus the ability to act in that person’s name. If the target is an administrator account, the damage extends to managing other users and to the configuration of the application itself.
Severity runs from medium to high, and that spread is real. The attacker needs two things: a way to get the id into the victim’s browser, and a victim who then actually logs in. Remove the first condition, because you share no subdomains, accept no session ids from URLs, and have no cross-site scripting, and the risk stays limited. Run the application on a shared domain with many subdomains, or expose a portal where administrators log in daily, and the same flaw becomes considerably more serious.
In business terms this translates into unauthorised transactions, access to personal data with a regulatory notification duty, and fraud committed in a real user’s name. That last point weighs heavily in a dispute: the logs show a normal login followed by normal behaviour, because technically nothing unusual happened.
How do you detect session fixation?
The test is short and needs no special tooling. Note the value of the session cookie before you log in, log in, and compare it afterwards. If it is unchanged, the application does not rotate the identifier and the vulnerability is present. Repeat that comparison around a second factor, a role switch and a password change, since many applications rotate only on the first login step.
Also check whether the server accepts a value you invented: set a cookie holding an id that was never issued, request a page, and see whether you get a working session. Look for the session id turning up in a URL, in redirects, in Referer headers or in shareable links, and confirm the cookie is scoped to the host it belongs to.
Automated scanners at best report that the cookie does not change; they miss the cases behind a second factor or inside an impersonation feature because they never walk that flow. Manual testing with two separate browser sessions does surface the behaviour. AssistSec reviews session handling as a standard part of a penetration test and shows, per finding, which id still worked after the victim had authenticated.
How do you prevent session fixation?
- Regenerate the session id on every privilege change. At login, after a successful second factor, on a role or tenant switch, on administrative impersonation, and on a password change.
- Destroy the old session server-side. Issuing a new id while the previous session keeps living only moves the problem somewhere less visible.
- Accept only identifiers you issued yourself. Enable your session library’s strict mode so an unknown value is rejected instead of adopted.
- Keep the session id out of the URL. Cookies only, never session ids in query strings or paths; those leak through logs, browser history and shared links.
- Set the cookie flags tightly.
HttpOnly,Secure, an appropriateSameSitevalue, and where possible the__Host-prefix so the cookie stays bound to one host. - Keep control of your subdomains. Any subdomain can set cookies for the parent domain, which makes a forgotten or hijacked subdomain a launch point for this attack.
- Limit lifetime and make logout mean something. An idle timeout, an absolute maximum lifetime and server-side invalidation on logout shrink the window in which a planted id is still worth anything.
Sources
Frequently asked questions
What is the difference between session fixation and session hijacking?
With session hijacking an attacker obtains an existing, already authenticated session, typically by stealing a cookie through cross-site scripting or unencrypted traffic. With session fixation the order is reversed: the attacker knows the id before anyone logs in and lets the victim authenticate it for them. The outcome is the same, but the attacker never has to intercept anything after the fact.
Are modern frameworks protected against session fixation by default?
Largely, yes. Laravel, Django, Rails, ASP.NET Core and Spring Security all rotate the session id at login as long as you use their own authentication mechanism. Problems appear in hand-rolled login screens, in a second login route bolted on beside the standard one, and around step-up authentication or impersonation, where privileges change without a formal new login.
Do HttpOnly and Secure cookies stop session fixation?
They help, but they do not solve it. HttpOnly keeps JavaScript away from the cookie and Secure stops it travelling over plain HTTP, which removes two ways of planting or intercepting an id. An attacker who sets the cookie from a subdomain, or who feeds an id through a URL parameter, still gets through. Only regenerating the id at login removes the attack at its root.
When should I regenerate the session id?
On every change in privilege level. That certainly means login, but also completion of a second factor, switching role or tenant, an administrator impersonating a user, and a password change. Regenerate on logout as well, and destroy the session server-side rather than merely clearing the cookie.
Related articles
- GlossaryPentestA penetration test (pentest) is a controlled attack on your systems by ethical hackers. Learn how a pentest works and what vulnerabilities it uncovers.
- VulnerabilitiesCWE-287A07:2021Broken authenticationBroken authentication explained: how attackers take over accounts through brute force, leaked passwords and predictable session tokens, and how to stop them.
- VulnerabilitiesCWE-79A03:2021Cross-site scripting (XSS)Cross-site scripting (XSS) lets attackers inject malicious scripts into web pages that run in visitors' browsers. Learn how XSS works and how to prevent it.
- VulnerabilitiesCWE-352A01:2021Cross-site request forgery (CSRF)Cross-site request forgery (CSRF) explained: how an attacker abuses a logged-in user's browser to perform unwanted actions, and how you prevent it.
- VulnerabilitiesCWE-319A02:2021Insecure transport and weak TLSInsecure transport explained: plain HTTP, missing HSTS, outdated TLS versions and cookies without Secure, and how to enforce HTTPS everywhere.