Choose your stack

Stacks without a card

How it happens

  • An anonymous visitor gets a session before signing in, often without anyone deciding so: storing a return URL, a flash message, a cart or a CSRF token creates one, and Spring Security keeps its CSRF token and the saved request in the HttpSession. If sign-in then writes the user into that same session, the ID issued before anyone authenticated now identifies the signed-in user.
  • The attacker obtains a real ID from the server and gets it into the victim's browser: by using a shared or kiosk computer first, by setting the cookie from a sibling subdomain they control, through script injected into the site, or through a session ID in a URL where the server accepts one. When the victim signs in, the attacker's copy of the ID is signed in too. Signing or encrypting the cookie doesn't help, because the ID is genuine.
  • Rotation is easy to get subtly wrong. Clearing the session's data keeps its ID (Flask's session.clear() with server-side sessions, ASP.NET Core's ISession.Clear()); a custom sign-in endpoint in Spring skips the fixation protection that only Spring Security's own sign-in filters apply; and hand-made rotation that copies the data under a new ID but leaves the old entry in the store keeps the old ID working.
  • Weak cookie attributes expose the ID. Without Secure, the browser sends the cookie over plain HTTP, where it can be read or changed in transit (CWE-614). Without HttpOnly, any script in the page, injected script included, can read it (CWE-1004). Without SameSite=Lax or Strict, browsers that don't apply Lax by default attach it to cross-site requests (CWE-1275). Without the __Host- prefix, a sibling subdomain can set a cookie of the same name that the browser sends to the application.
  • Framework defaults are incomplete and differ: express-session and Flask set HttpOnly but neither Secure nor SameSite; ASP.NET Core session state never sets Secure; Tomcat marks the session cookie Secure only when it knows the request arrived over HTTPS and sets no SameSite. Of the four, only Spring Security's sign-in filters and ASP.NET Core's cookie authentication give the user a new session at sign-in without extra code.
  • Sessions that never end turn any leaked ID into lasting access (CWE-613): express-session's MemoryStore keeps a session until the process restarts, Flask-Session keeps one for 31 days of inactivity by default, and an idle timeout on its own never ends a session the attacker keeps busy.

Fix principles

  • Issue a new session ID at every sign-in and every change of privilege, and invalidate the old ID in the store: req.session.regenerate() in express-session, app.session_interface.regenerate(session) in Flask-Session, Spring Security's session fixation protection (changeSessionId, the default), and in ASP.NET Core an authentication cookie, which SignInAsync issues afresh. Copy across only the data you need.
  • Keep sign-in state where it can be rotated: not in ASP.NET Core's ISession, which cannot change its ID, and not behind a custom Spring Security login endpoint unless it calls the SessionAuthenticationStrategy itself.
  • Accept session IDs only from the cookie, never from URLs (in Spring Boot, server.servlet.session.tracking-modes=cookie), and only IDs the server issued.
  • Set the session cookie's attributes explicitly: Secure, HttpOnly, SameSite=Lax (or Strict), and the __Host- prefix with Path=/ and no Domain, so that only this host, over HTTPS, can set or receive it.
  • End sessions on the server after a period of inactivity and at a fixed time after sign-in, whichever comes first (30 minutes and 8 hours in these cards, the top of the OWASP cheat sheet's example ranges: 15 to 30 idle minutes for low-risk applications, 4 to 8 hours absolute), and document the limits as ASVS 7.1.1 asks. A cookie's Expires attribute is not a server-side timeout.
  • Test it: replay the pre-login cookie after sign-in, read the Set-Cookie attributes, and move a test clock past both limits.

How the cards prove it

  • Run each card's tests in three modes: the fixed implementation must pass, and the vulnerable and the plausible-but-wrong implementations must fail. run-all.mjs does this for every card.
  • Every stack runs the same eleven tests on the same seed data and timeline. The exploit tests give the victim a browser that already holds the attacker's pre-login cookie, and Bob's signed-in cookie, and check that neither copy opens Alice's account after she signs in; they find the session cookie (the cookie without which the browser is signed out) and check its Set-Cookie line for HttpOnly, Secure, SameSite=Lax or Strict, and the __Host- prefix with Path=/ and no Domain; and they move a test clock 31 idle minutes, and 8 hours 5 minutes of activity every 10 minutes, past sign-in. The clock moves the framework's own expiry too: Date in express-session's MemoryStore, cachelib's time in Flask-Session, Tomcat's session access times, and the memory cache behind ASP.NET Core session state. Functional tests keep sign-in, a refused wrong password and two hours of normal use working.
  • Static analysis is thin. Semgrep's community rules have no session fixation rule for these stacks, check cookie attributes only for Express (CommonJS only, and two of those rules object to a correct __Host- cookie) and for explicit cookie writes, and never read Flask, Spring Boot or ASP.NET Core session settings. CodeQL has js/session-fixation (in the security-extended suite) for Express and cs/session-reuse for classic ASP.NET only. Each card's scannerRules records what was run and what it found.

References

  1. OWASP Session Management Cheat Sheet
  2. OWASP Top 10:2025 A07 Authentication Failures (CWE-384, CWE-613)
  3. OWASP Top 10:2025 A02 Security Misconfiguration (CWE-614, CWE-1004)
  4. OWASP Top 10:2025 A01 Broken Access Control (CWE-1275)
  5. CWE-384: Session Fixation
  6. CWE-613: Insufficient Session Expiration
  7. CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute
  8. CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag
  9. CWE-1275: Sensitive Cookie with Improper SameSite Attribute
  10. CAPEC-61: Session Fixation
  11. OWASP ASVS 5.0.0, V7 Session Management
  12. OWASP ASVS 5.0.0, V3 Web Frontend Security (3.3 Cookie Setup)
  13. OWASP WSTG: Testing for Session Fixation
  14. OWASP WSTG: Testing for Cookies Attributes
  15. MDN: Set-Cookie (cookie prefixes, Secure, HttpOnly, SameSite)

Attack patterns:

Guides on this weakness

Guide

Session management: cookies, timeouts, rotation and revocation

Model authentication as transitions in server authority, set session cookies and timeouts to current NIST and OWASP values, and verify fixation prevention, expiry, reauthentication, revocation and stolen-cookie defenses.

Guide

Protect cookie-authenticated actions against CSRF

Make the server distinguish an intended action from a request another site caused the browser to send.

Guide

SCIM deprovisioning: make account disablement remove every usable authority

Connect SCIM resource updates to current sessions, tokens, group-derived permissions and background jobs, with explicit tenant mapping, CAEP revocation events and a measurable revocation contract.

Guide

Configure HTTP security headers from a tested baseline

Send the response headers that make browsers enforce HTTPS, run only your own scripts, refuse hostile framing and keep cookies on one host, then prove they appear on every kind of response.

Guide

Defend JWTs against forgery, confusion and replay

Verify a JWT as an issuer-, type- and audience-bound assertion with an algorithm allowlist and trusted keys, closing the alg "none", key-confusion, header-injection, weak-secret and replay attacks.

Case study

Case study: the Okta support-system breach and session tokens in HAR files (2023)

How a service account password saved to a personal browser profile let an attacker download customers' HAR files from Okta's support system, replay the session tokens inside them against five customers, and stay unseen for two weeks; and why HAR files are credentials, admin sessions need binding, and every exposed credential must be rotated.

Case study

Case study: Citrix Bleed (CVE-2023-4966)

How a NetScaler memory over-read leaked session cookies that bypassed passwords and MFA in 2023, why patching did not end hijacked sessions, and how the pattern returned in 2025.