The premise
You are logged into your bank in one tab. In another tab, you open a malicious page. That page contains a hidden form that POSTs to bank.example/transfer with attacker-chosen amount and recipient fields. When the page submits, the browser attaches your bank cookie to the request — because the request is going to bank.example, and that's where your cookie lives. The bank sees an authenticated transfer. You see nothing.
The bug is not in the bank's authentication. The bug is that the bank can't tell the difference between a request its own page made and a request another page made on the user's behalf.
Try it — bank.example next to attacker.example
Two browser tabs side by side. You're logged into the bank. The other tab is a blog you stumbled onto. Click around — watch the network panel underneath.
Why this used to work everywhere
CSRF dominated the OWASP top 10 from 2007 to 2017. Two architectural choices made the web wide open by default:
- Cookies are ambient. Once a browser holds a cookie for a domain, every request to that domain carries it — regardless of which page caused the request. The browser does not ask the receiving server “was this request something the user explicitly chose to make?”
- Forms can cross origins. An HTML form on any page can
POSTto any URL. Same-origin policy stops scripts from reading the response, but the request itself goes through and the side effects (transfer money, change email, delete a row) happen anyway.
The defensive moves that closed most of this gap arrived gradually: the CSRF token pattern in the late 2000s, then the SameSite cookie attribute defaulted to Lax by Chrome in 2020. Most production frameworks now ship CSRF protection on by default. CSRF still happens; it just isn't free anymore.
The two defenses, and why both matter
1. CSRF tokens (synchronizer pattern)
The server embeds a random secret in every form it renders. When the form submits, the server checks the token before performing the action. An attacker's cross-origin form does not have the secret — their page can't read the legitimate page's HTML, so it can't know the value to submit.
2. SameSite cookies
The cookie itself carries a flag telling the browser when it may travel.
| Value | Behavior | Use case |
|---|---|---|
| SameSite=Strict | Cookie sent only on requests where the URL bar matches the cookie's domain. Even clicking a link from another site won't include it. | Bank dashboards, settings pages, anywhere first-click context is fine. |
| SameSite=Lax | Cookie sent on top-level GET navigations from other sites, but never on cross-site POSTs or iframe loads. Browser default since 2020. | Most session cookies. Balances usability against CSRF protection. |
| SameSite=None | Cookie sent on every cross-site request. Requires Secure. Pre-2020 default behavior. | Embedded third-party widgets that legitimately need cross-site cookies. Rare. |
SameSite=Lax alone defeats the classic CSRF pattern (attacker page auto-submits a cross-site POST). CSRF tokens still matter for any legitimate cross-origin flow, for older browsers, and as defense in depth.
Beyond the form post
- Login CSRF. Attacker submits the victim's browser into the attacker's account on a target site. Subsequent activity (uploaded photos, search history, saved payment methods) accrues to an account the attacker controls. Used against Google, Yahoo, and YouTube historically.
- Stored CSRF / CSWSH. Cross-site WebSocket hijacking: attacker page opens a WebSocket to a target the user is logged into, then reads messages over the long-lived connection. SameSite only recently extended to cover the upgrade request.
- GET-side-effect bugs. Any endpoint that mutates state on GET (e.g.
/logout,/delete?id=42) is CSRF-able via an<img src>tag from any site on the web. Don't put side effects on GET. RFC 7231 has been telling you for a decade. - JSON CSRF. Old browsers and misconfigured CORS allow an attacker to send a cross-site POST with a JSON body to an API. Newer browsers enforce a CORS preflight, which kills this; legacy systems are still vulnerable.
Notable incidents
- Netflix CSRF — 2006. Attacker could change account email, password, billing address via a single image tag. Netflix patched after disclosure.
- ING Direct — 2008. CSRF in transfer flow allowed attacker-controlled money movement. Disclosed in the foundational Bilal et al. paper that named the bug class.
- Twitter — 2010. CSRF could post tweets and follow accounts on a logged-in user's behalf. Patched same day.
- Endless small things — every framework's CSRF advisories archive. Django, Rails, Laravel, Spring — all have shipped patches for token-bypass bugs over the years. The bug class doesn't die; it just hides behind framework versions.