Defending Sessions
Every attack in this cluster targets one of a few weaknesses: a cookie the browser hands to the wrong party, an identifier that survives login or can be predicted, or a state-changing request the server trusts without proof it came from its own page. This page gathers the fixes in one place and maps each to the attack it closes, so a report can cite them together.
Cookie flags (the cheapest, highest-leverage fix)
Section titled “Cookie flags (the cheapest, highest-leverage fix)”Three attributes on the session cookie neutralise three different attacks:
| Flag | What it does | Attack it closes |
|---|---|---|
HttpOnly |
JavaScript can’t read the cookie (document.cookie omits it) |
XSS cookie exfiltration |
Secure |
Cookie is only sent over HTTPS | Plaintext sniffing |
SameSite=Lax or Strict |
Cookie isn’t sent on cross-site requests | CSRF |
Set-Cookie: session=…; HttpOnly; Secure; SameSite=Lax; Path=/SameSite=Lax is a sensible default (it still sends the cookie on top-level GET navigations); use Strict for high-value apps that don’t need cross-site entry. HttpOnly blocks the simplest XSS theft but not XSS itself - an attacker can still act in-session and scrape the DOM, so it is containment, not a cure (see XSS defense).
Regenerate the identifier on every privilege change
Section titled “Regenerate the identifier on every privilege change”Issuing a new session ID at login (and logout, and privilege elevation) and invalidating the old one closes session fixation: a value the attacker planted before login is dead the moment the victim authenticates. Use the framework’s built-in call, not a custom scheme.
// PHP - keep session data, swap the IDsession_regenerate_id(true); // true = delete the old session file// Java - invalidate, then get a fresh sessionsession.invalidate();session = request.getSession(true);// .NET - Session.Abandon() alone is not enough: the ID cookie persists// and is reused. Abandon AND overwrite/expire the session cookie.Session.Abandon();Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", "") { Expires = DateTime.Now.AddDays(-1) });Strong identifiers and lifecycle
Section titled “Strong identifiers and lifecycle”Closes weak-identifier prediction and brute-forcing:
- Randomness: use the platform CSPRNG via the framework’s session manager - never a custom generator, a counter, a timestamp, or
hash(username). A secure ID carries no reversible structure. - Validity scope: one identifier per session; don’t accept a pre-login ID post-login (see regeneration above).
- Expiry: enforce an absolute and an idle server-side timeout, and invalidate server-side on logout - don’t rely on deleting the client cookie.
Anti-CSRF: token plus SameSite
Section titled “Anti-CSRF: token plus SameSite”Closes CSRF and resists the token-bypass tricks:
- Synchronizer token pattern: a per-session (ideally per-request) random, unpredictable token on every state-changing request, verified server-side. This is the primary control.
- Reject the request when the token is missing, empty, malformed, or belongs to another session - the bypass categories exist precisely because apps skip one of these checks.
- Defence in depth:
SameSitecookies, and Origin/Referer checks done with exact origin comparison (not substring/regex, which bypasses defeat). None of these replace the token on their own.
Don’t trust redirect targets
Section titled “Don’t trust redirect targets”Closes open redirect (and the token-leak chains built on it):
- Prefer not taking a URL from the user at all. Where a destination is needed, map an opaque key to a server-side URL rather than redirecting to a user-supplied address.
- If a URL must be accepted, validate the scheme and the origin, not just the hostname. Allowlist the scheme to
http/https(rejectjavascript:,data:, and scheme-relative//evil) - a host check alone passesjavascript://trusted.example/%0Aalert(document.domain), whose host istrusted.examplebut which runs script in a DOM redirect. Then exact-match the host against an allowlist - not “starts-with”, “contains”, or a loose regex, each of which has a documented bypass. Parse with the same URL parser the redirect consumer uses, since parser differentials are themselves a bypass. - Keep session tokens out of URLs entirely, so a redirect can’t leak them via
Refereror history.
Transport and monitoring
Section titled “Transport and monitoring”- HTTPS everywhere, with HSTS, so there is no plaintext leg for sniffing and
Securecookies are never downgraded. - Hijacking has no clean single fix - a valid identifier is authority by design. Beyond eliminating the acquisition paths above, anomaly detection (sudden IP/User-Agent/geo change on a session) can flag a ridden session, and short timeouts shrink the window. Binding a session to coarse client attributes can help but breaks mobile/roaming users, so apply it judgmentally.
For the report
Section titled “For the report”Lead with the control that closes the finding you actually demonstrated, then the defence-in-depth layers:
- Fixation → regenerate the session ID on login/logout (framework call; mind the .NET cookie caveat).
- XSS cookie theft →
HttpOnly+Secure; and fix the XSS itself (XSS defense). - CSRF → synchronizer tokens with full validation, plus
SameSite. - Sniffing → HTTPS + HSTS +
Secure. - Weak identifiers → CSPRNG generation, proper scope/expiry.
- Open redirect → key-to-URL mapping, or scheme-and-exact-origin validation (not host alone), parsed as the redirect consumer parses it; keep tokens out of URLs.
Avoid presenting a single flag or a Referer check as the whole fix - each closes one path, and this cluster exists because real apps ship with only some of them in place.
Related
Section titled “Related”- XSS defense - fixing the XSS that
HttpOnlyonly contains - Auth: cookie tampering - cookie attribute handling in depth
- Auth: JWT - when the “session” is a signed token with its own failure modes