Client-side

Cross-Site Request Forgery (CSRF): The Complete Practical Guide

What CSRF is, how to exploit it, bypass tokens and SameSite and Referer defenses, and how to prevent it. With free labs to practice on.

Published

Cross-Site Request Forgery (CSRF): The Complete Practical Guide

Cross-site request forgery tricks a logged-in user's browser into sending a request the user never intended, using the cookies the browser attaches automatically. The application sees a perfectly authenticated request and does what it says: change the email, grant the role, move the money. The attacker never sees the response and never steals the session, they just borrow it for one request. This guide covers how CSRF works, how to defeat the defenses meant to stop it, and how to prevent it properly. Want to feel it rather than read about it? Sign up free and try it on a live target.

What is CSRF?

The mental model is the confused deputy, and the browser is the deputy. Your browser holds the user's session cookie for a site and dutifully attaches it to every request to that site, no matter who initiated the request. CSRF abuses that. An attacker's page issues a request to the target, the browser attaches the victim's cookies out of habit, and the target cannot tell the request was not meant. The attacker supplies the intent, the browser supplies the authority.

Crucially, the attacker does not need to read the response. Same-origin policy stops them seeing it, but they do not care, because the damage is done by the request itself: the email is already changed. CSRF is a write primitive, not a read one.

The three preconditions

CSRF needs all three of these to be present:

  • A worthwhile action. Something that changes state and is worth forging: a password or email change, a role grant, a funds transfer, a settings update. Read-only actions are not targets.
  • Cookie-based (ambient) authentication. The session is carried by a cookie the browser attaches automatically. If the app requires a token in a header that only its own JavaScript adds, a cross-site page cannot replay it.
  • Predictable request parameters. The attacker must be able to construct the whole request in advance. If a request contains an unguessable value the attacker cannot know (like a CSRF token), they cannot forge it.

Take away any one and CSRF does not work. Prevention is mostly about removing the third (unguessable token) or the second (stop trusting the ambient cookie alone).

How a CSRF attack is delivered

The victim just has to visit an attacker-controlled page while logged in to the target. That page carries the forged request. For a POST, an auto-submitting hidden form:

<body onload="document.forms[0].submit()">
  <form action="https://target.example/account/email" method="POST">
    <input type="hidden" name="email" value="[email protected]">
  </form>
</body>

For a state-changing GET, no form is even needed, an image tag fires it on load:

<img src="https://target.example/account/[email protected]">

No clicking, no interaction. The page loads, the request goes out with the victim's cookies, and the account is changed.

XSS vs CSRF

They are often confused and are genuinely different bugs. CSRF forges a request from outside the app with no script execution, while XSS runs script inside the app's origin. XSS is strictly more powerful, because script in the origin can read an anti-CSRF token from the DOM and submit a fully valid request, defeating CSRF defenses entirely. XSS vs CSRF lays out what each needs, where they overlap, and why "we have CSRF tokens" is no comfort if XSS exists.

How to find CSRF

The method is consistent:

  • Find the state-changing requests. Map every action that writes: account settings, role changes, transfers, deletes.
  • Check the token. Is there a CSRF token? Remove it, blank it, swap in another account's token, and see if the request still succeeds. Most CSRF findings are a token that is not really validated.
  • Flip the method. If a token is enforced on POST, try GET, or a method-override parameter. Method-scoped validation is a classic hole.
  • Inspect the cookie. Is the session cookie SameSite Lax, Strict, or None? Is the action reachable by a top-level GET?

Bypassing CSRF token validation is where most real work happens.

Why defenses fail

Three defenses are meant to stop CSRF, and each has a way to go wrong:

  • CSRF tokens that are not checked, checked only when present, not bound to the session, or enforced on only one method. See bypassing token validation.
  • SameSite cookies, which still allow top-level GET under Lax, have a grace window for cookies with no explicit SameSite, treat sibling domains as same-site, and offer no protection at all when set to None. See bypassing SameSite restrictions.
  • Referer validation, which is advisory, strippable, and frequently matched with a naive substring or prefix check. See bypassing Referer defenses.

And when the token is genuinely solid, CSRF via XSS reads it from the page and submits anyway.

A worked example

Say the app lets a user change their account email with a POST to /account/email, and it includes a CSRF token in the form. You test the token first: you remove the token parameter entirely and replay the request, and it still succeeds. The token was present in the form but never validated server-side, so the "protection" was cosmetic. Now you build an auto-submitting form pointing at /account/email with the email set to an address you control, host it, and get the logged-in victim to open it. Their browser posts the change with their session cookie, the email is now yours, and you trigger a password reset to your inbox to finish the takeover. The whole attack is the one broken check: a token that was generated but never enforced.

Prevention

  • Use a synchronizer token that is unpredictable, bound to the user's session, and verified on every state-changing request regardless of HTTP method.
  • Set SameSite=Lax as a baseline (Strict for the most sensitive apps), but do not rely on it alone given the GET and grace-window edges.
  • Prefer a double-submit cookie only when signed and tightly scoped, because a naive one is defeated by a subdomain that can set the cookie.
  • Check the Origin header (with Referer as backup) on sensitive endpoints as defense-in-depth, never as the only control.
  • Never let a GET request mutate state. Read-only GET removes a whole class of CSRF outright.

The full detail is in preventing CSRF.

Where it connects

CSRF rides a session, so it sits right next to authentication: the forged request only matters because the victim is logged in, and a logout or login CSRF attacks that session directly. What the forged request reaches is an authorization decision, which is the domain of broken access control. And the single cleanest way to beat every CSRF defense is to already have XSS, because script in the origin reads the token and forges the request from the inside.

Where to go next

Work through the cluster in order, or jump to what you need. Start with how CSRF works if the mechanism is new, then XSS vs CSRF and bypassing token validation. When you want every payload in one place, the CSRF cheat sheet has them. And when you are ready to break something real, sign up free and start hacking.