Quick answer
CSRF (Cross-Site Request Forgery) forces a user to perform unwanted actions on an app where they're authenticated. It targets state-changing requests (change email, transfer money) mounted from another domain. It's mitigated with CSRF tokens validated on the server, SameSite cookies, Origin/Referer validation, and rejecting unexpected Content-Types. It breaks when the developer validates any of those defenses poorly.
How the attack works
CSRF exploits the fact that the browser automatically attaches cookies to cross-site requests. If the victim is logged into bank.tld and visits attacker.tld, an attacker's page can make the browser send a POST to bank.tld/transfer with the session cookie included.
If the server only validates the cookie, the transfer executes as if the victim had initiated it. That's CSRF.
Common defenses (and how they break)
1. CSRF Tokens
The server injects a random token into every form. The client sends it in every request. If it doesn't match → 403.
Typical bypasses:
- Token absent. Remove the parameter from the request → does the server still accept it? Some backends only validate when it's present.
- Empty token.
csrf_token=(empty string) → some validators accept it. - Weak token. Is it a fixed value per account? Is it reused? Capture yours, build the PoC for the victim with your token.
- Only part of the token validated. If only half or a prefix is validated, send any token with that valid prefix.
- Token expires but isn't invalidated. If reusing old tokens works → CSRF.
2. SameSite Cookie
A cookie attribute that controls when it's sent cross-site:
SameSite=Strict— never cross-site. Kills CSRF at the root.SameSite=Lax(current default in Chrome/Firefox) — only on top-level navigation (link click). NOT on cross-site POST → blocks classic CSRF.SameSite=None; Secure— always sent. Vulnerable.
Bypasses:
- If the app uses
SameSite=Lax, GET requests do cross. If a critical action accepts GET (/password_change?new=...), CSRF is possible. - Legacy apps with cookies that don't declare
SameSitebehave asNonein some browsers.
3. Origin / Referer Header
The server compares the Origin (or Referer) header with its own domain.
Bypasses:
- Remove Referer.
<meta name="referrer" content="no-referrer">on the attacker page. If the server "lets it through" when there's no Referer → bypass. - Partial spoofing.
Referer: example.tld.attacker.tld(a subdomain that contains the target domain as a prefix). If the check isstartsWith()orcontains(), it passes. - Origin null. Sandboxed iframes send
Origin: null→ if the server accepts it, bypass.
4. Content-Type validation (for JSON APIs)
If the endpoint requires Content-Type: application/json, it's not trivial to mount it in a classic HTML form. But:
- A form with
enctype="text/plain"can generate JSON-shaped bodies:
<form action="https://target.tld/api/email" method="POST" enctype="text/plain">
<input name='{"email":"atk@evil.tld","x":"' value='"}'>
</form>
The resulting body: {"email":"atk@evil.tld","x":"="} — valid JSON if the server parses it.
- Change the Content-Type. If the API also accepts
application/x-www-form-urlencodedin addition to JSON → simple form CSRF. - Plain/text. Some backends ignore the Content-Type and parse the body directly.
Bypass in JSON requests
Real example:
<html>
<head><meta name="referrer" content="unsafe-url"></head>
<body>
<form name="hacker" method="POST" action="https://target.tld/api/phone.json" enctype="text/plain">
<input type="hidden" name='{"phone":"+34666000000","a":"' value='"}'>
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Body sent: {"phone":"+34666000000","a":"="}. If the server parses it as JSON, the attack succeeds.
CSRF + ClickJacking
If the server blocks CSRF but the page can be embedded in an <iframe>, you mount a clickjacking:
<iframe src="https://target.tld/settings/password" width="500" height="500"></iframe>
By positioning the iframe over a visible attacker button, the click lands on the target iframe. The browser attaches cookies + the server processes the action.
Mitigation: X-Frame-Options: DENY or CSP frame-ancestors 'none'.
Endpoints where to hunt CSRF
- Email change (leads to ATO if the user doesn't re-confirm).
- Password change.
- 2FA settings change (disabling 2FA → ATO).
- Adding SSH keys / API keys to the profile.
- Transfers / payments / subscriptions.
- Account deletion (defacement vector).
- Linking OAuth/SSO accounts (can lead to takeover if the victim links the attacker's account).
- Role / permission changes in apps with an admin panel.
Pattern: CSRF relay endpoints
Some apps have internal endpoints like /api/proxy?url=/api/endpoint that forward internally and automatically inject the user's CSRF token. For the attacker, all it takes is for the victim to visit the URL — they don't need to steal the token.
Look for endpoints named proxy, redirect, forward, relay, dialog, _DONOTUSE, _DEBUG, _INTERNAL. If they forward authenticated requests → possible universal CSRF.
Hunting checklist
- Does the server accept the request without a CSRF token?
- Does an empty token pass? Does another account's token pass?
- Are there sensitive endpoints that accept GET? (check methodOverride too).
- Is
SameSitedeclared? Is itLax/None? - Is Origin/Referer validated strictly or just "contains"?
- Is the Content-Type validated or ignored?
- Is there a relay/proxy endpoint that injects the token automatically?
- Is
X-Frame-Optionspresent, to rule out clickjacking?
Related labs
Practice CSRF + bypasses with real apps that validate tokens, Origin and Content-Type: CSRF labs.
Practice this in a lab
Csrf
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
Cookie flags — Secure, HttpOnly, SameSite and why they matter
HttpOnly blocks XSS-to-cookie, Secure forces HTTPS, SameSite kills CSRF. How they break and what to report when they're missing on sensitive cookies.
JWT — vulnerabilities, bypasses and claim manipulation
alg=none, RS256→HS256 confusion, kid SQLi/path traversal, jku spoofing, secret cracking with hashcat. How to hunt poorly verified JWTs.
OAuth attacks — state CSRF, redirect_uri bypass, code/token leakage
The missing state parameter, poorly validated redirect_uri, response_type confusion. How to steal OAuth tokens and force account linking.