Session Hijacking
The attacker does not need the password: possession of the session token is possession of the account, and every defense is either about preventing the token from escaping or about limiting what it is worth once it has.
Frame the problem
Security starts with a concrete asset, attacker capability and trust crossing.
Every path the token takes to escape
A session token is a bearer credential: the server validates the token, not the bearer. That single property means the entire security of the session reduces to "did the token stay where it belongs", and there are more ways for it to leave than teams usually enumerate.
The paths are worth listing explicitly, because each has a different owner and a different fix. Some are code (XSS, tokens in URLs), some are configuration (missing Secure, missing HSTS), some are operational (logs, error reporting, session replay tools), and at least one is entirely outside your control (malware on the device).
| Path | How it happens | Control |
|---|---|---|
| XSS reads it | Injected script reads localStorage or a non-HttpOnly cookie | HttpOnly cookies; CSP; fix the XSS |
| Plaintext transport | One http:// request carries the cookie in the clear | Secure attribute + HSTS preload |
| URL leakage | Token in a query string reaches logs, history, Referer | Never put tokens in URLs |
| Log capture | Access logs, APM traces, error reports record headers or bodies | Redact by field name at the logging layer |
| Session replay tooling | Third-party analytics records the DOM and network traffic | Exclude auth surfaces; review vendor scope |
| Referrer | Outbound link leaks a token-bearing URL to a third party | Referrer-Policy: strict-origin-when-cross-origin |
| Shared device | Session persists after the user walks away | Idle timeout; explicit logout; short absolute lifetime |
| Fixation | Attacker plants a known id before login and the app reuses it | Rotate the session id at login |
| Device malware | Reads the browser profile directly | Outside your control; limit with short lifetimes and step-up |
Binding: useful, and less than it sounds
Since the server cannot tell the bearer apart from the user, a natural idea is to bind the session to something about the client so that a stolen token fails elsewhere. This works, partially, and the partiality matters.
IP binding breaks legitimate users constantly: mobile networks change addresses, corporate egress rotates, users move between Wi-Fi and cellular mid-session. Binding to a coarse network property (an autonomous-system number, or a country) as a *signal* rather than a hard requirement is defensible; binding to an exact address produces a support queue and gets removed.
Device or user-agent binding is cheap and catches unsophisticated theft, but the user agent is attacker-controlled — it travels in the request the attacker copied. Treat a mismatch as a strong signal for step-up, not as proof.
The genuinely strong version is cryptographic token binding, where the client proves possession of a private key on each request rather than merely presenting a bearer string. This removes the bearer property entirely: a copied token is useless without the key. It is what mutual TLS achieves between services and what DPoP-style proof-of-possession schemes achieve for tokens. Adoption in browsers remains limited, so for most applications the practical stance is: reduce escape paths, shorten lifetimes, require step-up for dangerous actions, and treat binding signals as inputs to risk scoring rather than as gates.
Limiting what a stolen session can do
Since prevention is imperfect, the second question is what an attacker gets. A session with unlimited authority over the account gives full takeover; a session that cannot change the email address, cannot disable MFA, and cannot create an API token gives temporary access that ends when the session does.
Step-up authentication on account-takeover primitives is the highest-value control here and is missing from most applications. It costs one re-authentication prompt on a handful of rare operations and removes the attacker's ability to convert a temporary foothold into permanent control.
Short absolute lifetimes cap the window. Immediate revocation ends it on demand. Visible session lists let users notice and act themselves — a surprisingly effective detection channel, because the user knows which devices are theirs and your anomaly detection does not.
And notification closes the loop: emailing the previously-known address on new-device login, email change, MFA change and token creation gives the legitimate user a chance to intervene, provided the notification cannot be suppressed by the attacker who just changed the address. Always notify the *old* address on a change of address.
- Step up for: change email, change password, disable MFA, create API token, add OAuth app, change payout details.
- Absolute lifetime caps the exposure; idle timeout limits abandoned-device risk.
- Show users their sessions with device, location and last-seen, and let them revoke individually or all at once.
- Notify the previously-known contact on every security-relevant change, and make the notification unsuppressable.
- Rotate the session id at login and at every privilege change, without exception.
Key points
- Session tokens are bearer credentials: the server validates the token, not the person, so theft is complete impersonation.
- Enumerate every escape path — XSS, plaintext, URLs, logs, replay tools, referrers, shared devices, fixation — and own each one.
- IP and user-agent binding are risk signals, not gates; strict binding breaks legitimate users and gets removed.
- Step-up authentication on takeover primitives is the cheapest way to bound what a stolen session achieves.
- Give users visible, revocable session lists — they detect theft better than your anomaly rules do.
Boundary control exercise
This lesson uses the shared boundary-control exercise.
Follow the attack
Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.
- 1Attacker → position: obtain script execution, a log, a plaintext hop, or physical access.
- 2Position → token: read the cookie, the storage entry, the URL, or the log line.
- 3Token → replay: present it from their own client; the server sees a valid session.
- 4Session → permanence: change the recovery email, add an API token, or enrol a new MFA factor before the session expires.
- Full impersonation with no credential prompt and no login event to alert on.
- Actions attributed to the legitimate user throughout the audit trail.
- If takeover primitives are reachable, temporary theft becomes permanent account control.
Defend, detect, recover
One prevention is a single point of security failure. Layer it and make failure observable.
- • Eliminate escape paths first: `HttpOnly` and `Secure` cookies, HSTS, no tokens in URLs, redaction in logging, scoped analytics.
- • Rotate at login and privilege change; enforce idle and absolute timeouts server-side.
- • Require step-up for all account-takeover primitives.
- • Use CSP to reduce the chance that an injected script can exfiltrate anything even if it runs.
- • Alert on one session used from two distinct locations or device fingerprints in a short window.
- • Alert on mid-session changes to user agent or device fingerprint.
- • Alert on takeover primitives invoked from a session that has not re-authenticated.
- • Revoke all sessions for the user, not just the suspicious one.
- • Audit for persistence created during the window and remove it.
- • Notify out of band and require re-authentication with a second factor.
- • Malware on the user's device defeats every server-side control.
- • A stolen token used from the same network and device as the user is not distinguishable by any signal you have.
- • Third-party scripts on your origin operate inside the session by design.