Chapter 96·Intermediate·10 min read
CSRF Explained: How a Random Site Can Act as You — and How to Stop It
What is CSRF (cross-site request forgery)? A plain-English explanation of how another website can make requests as your logged-in user, why cookies are the root cause, and the two modern fixes: SameSite cookies and anti-CSRF tokens.
July 25, 2026
XSS got the attacker's code running on your site. CSRF is stranger and, in a way, more elegant: the attacker runs no code on your site at all. They simply get the victim's browser to send a request — and let the browser's own helpfulness do the rest.
This attack hinges on one detail of how cookies and the same-origin policy interact, so those two chapters are the ideal warm-up. Here's the trick, and the two clean fixes.
The mechanism, in one story
You're logged into yourbank.com. Your session lives in a cookie. In another tab, you visit cute-cat-pics.com, which — unknown to you — contains this:
<form action="https://yourbank.com/transfer" method="POST" id="f">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="5000">
</form>
<script>document.getElementById('f').submit()</script>The page auto-submits that form. Your browser sends a POST to yourbank.com/transfer — and, because it's a request to yourbank.com, the browser automatically attaches your yourbank.com session cookie. The bank's server sees a perfectly authenticated request from your session and executes the transfer.
Notice what the attacker never needed: your password, your cookie's contents, or any code on the bank's site. They only needed you to be logged in and to load their page. Recall from the CORS chapter that the same-origin policy blocks the attacker from reading the response — but the request still fires and the state change still happens. For a transfer, reading the response was never the point.
What CSRF can and can't do
CSRF is a state-changing attack. It's dangerous exactly where a request does something:
- Transfer money, make a purchase
- Change your email or password (a classic account-takeover chain: change email → trigger password reset → own the account)
- Delete data, add an admin user, change privacy settings
What it can't do is read data back — the same-origin policy still withholds the response from the attacker's page. So CSRF steals actions, not information. (When you need to steal information, that's what XSS is for — the two are often confused precisely because they're complementary.)
This also tells you where CSRF protection matters most: any endpoint that changes state. GET requests should never change state in the first place (a rule that predates CSRF but neatly sidesteps a whole attack class), and state-changing verbs — POST, PUT, DELETE — are the ones to defend.
Fix #1: SameSite cookies
The root cause is "cookies get sent on cross-site requests." Modern browsers let you switch that off with one cookie attribute: SameSite.
| Value | Behaviour |
|---|---|
Strict | Cookie never sent on any cross-site request. Maximum safety; can log users out when following links in from elsewhere. |
Lax | Cookie withheld on cross-site subrequests (forms, fetches, images) but sent on top-level navigations you click. The modern browser default. |
None | Cookie always sent cross-site — must be paired with Secure. For deliberate cross-site use only. |
SameSite=Lax alone kills the auto-submitting-form attack above: the forged POST from cute-cat-pics.com is cross-site, so the browser withholds the bank cookie, so the request arrives unauthenticated and fails. Because browsers now default to Lax, a large share of the classic CSRF surface closed by default over the last few years — one of the web's quiet security wins.
Fix #2: anti-CSRF tokens
The token approach attacks the root cause from the other side: if the danger is that the attacker can forge a valid request, make valid requests require a secret the attacker cannot know.
The synchronizer token pattern:
- When the server renders your form (or your SPA boots), it hands the browser a large random CSRF token bound to the session.
- Your legitimate requests include it — a hidden form field, or a header your JavaScript sets.
- The server rejects any state-changing request whose token is missing or wrong.
Now replay the attack: cute-cat-pics.com can still auto-submit a form to the bank, but it has no way to obtain the victim's token — the same-origin policy blocks it from reading the bank's pages to scrape it, and it can't guess a large random value. The forged request arrives tokenless and is refused. (A common variant, the double-submit cookie, sends the token both as a cookie and as a header and checks they match — handy for stateless APIs, since the forging site can't set a matching custom header cross-origin without a CORS grant.)
One critical interaction: CSRF tokens assume you don't have XSS. An attacker running JavaScript on your origin via XSS can simply read the token from the page. That's not a token weakness — it's the reminder that these defences are layers, and a hole in one undermines the others.
Putting it together
Modern CSRF defence is layered, and pleasantly low-effort:
SameSite=Lax(orStrict) on session cookies — the default now, and the biggest single win.- Anti-CSRF tokens on state-changing endpoints — the belt to SameSite's suspenders, and the layer that covers the gaps.
- Never change state on
GET— closes the top-level-navigation loophole for free. - Many frameworks (Django, Rails, Spring, ASP.NET) ship CSRF token protection built in — the failure mode is usually disabling it, not lacking it.
Recap
- CSRF makes the victim's browser fire a request at a site they're logged into, and the browser auto-attaches the session cookie — no password, no cookie theft, no code on your site.
- The root cause is ambient cookie attachment: cookies ride along on cross-site requests your code never authorised.
- It's a state-changing attack (transfers, email changes, deletions); it can't read responses back — the same-origin policy still blocks that.
SameSite=Lax, now the browser default, stops cookies riding cross-site subrequests and closes most of the classic attack.- Anti-CSRF tokens demand a per-session secret the forging site can't obtain — the layer that covers SameSite's gaps.
- The layers assume no XSS — script on your origin can read the token, so these defences reinforce rather than replace each other.
Both attacks so far target the browser. Next we turn to the server's side of the trust boundary and the injection that hits your database directly. Continue to SQL injection, explained.