Safeguard
Application Security•

CWE-384 Session Fixation: How It Happens and How to Fix It

If login keeps the old session ID, an attacker who planted it owns the session. CWE-384 explained with Java and Python fixes and the checks that catch it.

Safeguard Security Team
5 min read

Session fixation is a simple trick with a serious result. The attacker gets a valid session identifier from your site, makes the victim use it, and waits for the victim to log in. Because the application never changed the identifier, the attacker now holds an authenticated session. CWE-384 defines it as authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier.

It hits any web application that uses server side sessions or session cookies, whatever the language. MITRE classes it as a compound weakness, meaning it appears when several conditions line up.

How it happens

MITRE describes the scenarios that produce it:

  • A web application authenticates a user without first invalidating the existing session, and keeps using the session already associated with the user.
  • An attacker is able to force a known session identifier on a user, so that once the user authenticates, the attacker has access to that session.
  • The application or container uses predictable session identifiers.

In the generic exploit, the attacker creates a new session on the application and records its identifier. They then cause the victim to associate, and possibly authenticate, with the server using that identifier. How they push the ID onto the victim varies: a crafted link when IDs are accepted in the URL, or another way of setting a cookie.

Here is a vulnerable login handler in a Flask style app:

@app.route("/login", methods=["POST"])
def login():
    user = check_credentials(request.form["u"], request.form["p"])
    if user:
        session["user_id"] = user.id     # same session ID as before login
        return redirect("/home")

The session cookie was issued before login. After login it is the same cookie, now with an authenticated identity attached. Anyone who knows that cookie value is logged in as the user.

The same bug in Java servlet code:

HttpSession session = request.getSession();      // reuses existing session
session.setAttribute("user", authenticatedUser);

A real case from the record: CVE-2022-2820, where website software for game servers did not properly terminate user sessions, allowing possible session fixation.

What an attacker gains

MITRE lists the consequence as access control: gain privileges or assume identity. The attacker becomes the victim for as long as the session lives. That can mean reading private data, changing account settings, or making purchases and transfers, whatever the real user could do. The victim notices nothing because their own login worked normally.

How to find it

The record lists no detection methods, but this weakness is easy to test by hand.

  1. Open the site in a fresh browser profile and note the session cookie value before logging in.
  2. Log in.
  3. Compare the cookie. If the value is unchanged, the application keeps the pre-login identifier and is exposed.

Repeat for every action that raises privilege: login, MFA completion, role switch, password change. Also check whether the app accepts session identifiers in the URL or from query parameters, and whether identifiers look sequential or guessable.

Code review is quick too. Find the login handler and look for the call that regenerates the session ID. If there is none, that is the finding. A dynamic scan of a running app can run the same before and after comparison automatically.

How to fix it

MITRE's mitigations are direct.

  1. Invalidate any existing session identifiers before authorizing a new user session.
  2. For platforms such as ASP that do not generate new session ID cookie values, use a secondary cookie. Set a random value in a second cookie and store the same value in a session variable. If the two ever do not match, invalidate the session and force a new login.
  3. Operationally, an application firewall that detects these attacks can help when the code cannot be fixed, for example when a third party controls it. MITRE rates its effectiveness as moderate and warns it may not cover all input vectors and can reject legitimate requests. Treat it as a stopgap, not the fix.

Fixed Flask version:

@app.route("/login", methods=["POST"])
def login():
    user = check_credentials(request.form["u"], request.form["p"])
    if user:
        session.clear()                  # drop the old session data
        session["user_id"] = user.id     # framework issues a fresh ID
        return redirect("/home")

Check that your framework really issues a new ID when the session is cleared. If it does not, rotate it explicitly with the framework's API.

Fixed Java version:

request.getSession().invalidate();
HttpSession fresh = request.getSession(true);
fresh.setAttribute("user", authenticatedUser);

Servlet containers also offer request.changeSessionId() for the same purpose.

Add a few supporting habits: never accept session IDs from URLs, set cookies with the Secure and HttpOnly flags, use identifiers generated by a cryptographically secure source, and expire sessions on logout on the server side, not just by deleting the cookie.

Try it yourself

Click through it at your own pace, or open it full screen.

Quick check

The app uses HTTPS everywhere. Is session fixation still possible? Yes. HTTPS protects the identifier in transit, but fixation works by getting the victim to use an identifier the attacker already knows. The fix is to issue a new identifier at login.

What is the one line test for this bug? Compare the session cookie before and after login. If it is the same value, the application is vulnerable.

Is a web application firewall enough? MITRE rates it as moderate protection and suggests it as an emergency measure or defense in depth, not a replacement for invalidating the old session.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.