OWASP Top 10 Deep Dive · 4 of 8

CSRF — Your Browser, Their Request

Cross-Site Request Forgery: a malicious page tricks the user's browser into making a request to your site, where the user is already logged in. The browser cheerfully attaches the session cookie; your server runs the action — transfer money, change email, delete account — as if the user wanted it. The attacker never touched the user's credentials.

SameSiteCSRF TokensCookiesOrigin Header
← Back to Security
Quick Facts

What CSRF Is

Basic Concepts

  • The setup: the user is logged into your-bank.com. They visit evil.com. evil.com contains a hidden form or image whose URL is your-bank.com/transfer?to=attacker&amount=1000.
  • The browser betrays you: it sends the cookie for your-bank.com with that request automatically — that's how cookies have always worked.
  • The server can't tell the difference between a legitimate click on the bank page and a forged click from evil.com — unless it's checking specifically.
  • Affects state-changing actions only: POST, PUT, DELETE that have side effects. GET requests should never have side effects (and if they do, they're vulnerable to even simpler attacks).
  • Modern era: SameSite cookies have made CSRF much harder by default, but it's not gone — and several other defenses are still required.
The Attack

How CSRF Plays Out

The Classic Form Auto-Submit
<!-- on evil.com -->
<form action="https://your-bank.com/transfer"
      method="POST" id="f">
  <input name="to" value="attacker"/>
  <input name="amount" value="1000"/>
</form>
<script>document.getElementById('f').submit();</script>

Victim loads the page; the browser submits the form to the bank with the bank's cookies; transfer goes through. No interaction beyond visiting the malicious page.

GET-Based CSRF

If GET /unsubscribe?id=42 changes state, an attacker can trigger it with <img src="https://your-app/unsubscribe?id=42">. The image "fails to load" but the request fired with cookies attached. Rule: GET must be safe — never have side effects.

JSON CSRF

"We accept JSON, so we're safe." Not always — if the server accepts application/x-www-form-urlencoded too (common Spring/Express defaults), a form post with a payload that looks like JSON works. Lock down content-type expectations and reject non-JSON for JSON endpoints.

Defenses

How to Stop CSRF

1. SameSite Cookies

The biggest shift in CSRF defense in years. SameSite=Lax (the modern browser default) means cookies aren't attached to most cross-site requests. SameSite=Strict excludes even top-level navigations. With Lax/Strict, classic CSRF largely fails.

Caveats:

  • SameSite is per-cookie; set it on every session cookie.
  • Older browsers don't enforce it — you still need defense in depth.
  • Some flows (third-party embeds, federated SSO) require SameSite=None + Secure; those need other CSRF defenses.
2. CSRF Tokens (Synchronizer Token Pattern)

The server generates a random token per session (or per form), stores it server-side, and embeds it in every form/request as a hidden field or header. The server rejects any state-changing request whose token doesn't match. Cross-site attackers can't read the token (same-origin policy stops them), so they can't forge a valid request.

Frameworks ship this. Spring Security CsrfFilter, Django {% csrf_token %}, Rails protect_from_forgery, ASP.NET AntiForgeryToken. Use them; don't roll your own.

3. Double-Submit Cookie Pattern

For stateless APIs: send a random token in both a cookie and a request header. The server checks they match. Cross-site attackers can read neither, so they can't forge the match. Common with SPA + JWT setups.

4. Custom Header Requirement

Browsers don't allow cross-origin requests with custom headers (without a CORS preflight that your server controls). Require a header like X-Requested-With: XMLHttpRequest or X-CSRF-Token for state-changing endpoints; reject otherwise. Simple, effective for SPAs and JSON APIs.

5. Verify Origin / Referer

Modern browsers send Origin on POST. Reject requests whose Origin isn't your own site. Belt-and-braces with SameSite cookies; both can be defeated alone, but together they cover real-world cases.

6. Re-authenticate for Sensitive Actions

Bank transfers, password changes, email changes, billing updates — require the user to re-enter their password or pass an MFA challenge. Even a successful CSRF can't satisfy that.

Reality Check

What's Different in 2026

Browser defaults have done most of the work for the common case:

  • SameSite=Lax is the default for cookies that don't specify SameSite. Classic CSRF against most sites fails out of the box.
  • Cross-origin requests trigger CORS preflights; servers control whether to allow them.
  • Bearer tokens stored in localStorage aren't sent automatically by the browser — they're immune to CSRF, but vulnerable to XSS instead. Pick your tradeoff.

The shifted attack surface: CSRF mostly survives now in cases requiring SameSite=None (third-party embeds, federated login flows), in legacy apps without SameSite set, and in mobile/desktop apps where browser CSRF defenses don't apply. Enable framework CSRF protection and SameSite together — they cover different cases.

Continue

Other OWASP Top 10