Cross-Site Request Forgery
Summary
CSRF reports in this corpus cluster around four root causes:
- missing or unvalidated anti-CSRF tokens on state-changing endpoints,
- state-changing actions exposed via GET,
- cookies without SameSite=Lax/Strict so cross-origin POSTs carry session, and
Top Affected Components / Targets
Account profile / email change / password change endpointsOAuth/SSO authorize URLs missingstateparameter (Bitbucket, Streamlabs+Twitch, Periscope+FB)GraphQL endpoints accepting GET (GitLab /api/graphql)Cart/wishlist/coupon endpoints (Starbucks Teavana, securevetsource cart)Pet / item delete endpoints (Mars royalcanin, securevetsource)Logout endpoints (enjin /logout, wakatime)Newsletter / contact / support forms (sifchain, Rockstar support)Admin panels for content management (WordPress, ImpressCMS, ExpressionEngine, Apache Airflow DAG trigger)Comment/notification endpoints (WordPress, ExpressionEngine, Discourse)Mobile deeplinks accepting state changes (snapchat://unlock)
Common Attack Vectors
Auto-submitting HTML form on attacker-controlled page; victim's authenticated cookies sent<img src> / <a href> firing GET that performs state changeGraphQL mutation moved from POST to GET to avoid X-CSRF-Token headerOAuth flow without state param: attacker links victim account to attacker IdPNull byte / whitespace injection instateparameter to bypass strict-equality checksWeb cache deception (CloudFlare) to capture victim's CSRF token from cached pageFlash 307-redirect trick to land cross-origin POST with custom Content-TypeSame parent-domain XSS pivots SameSite=Lax cookies to cross-subdomain CSRF (Argo CD case)Client-Side Path Traversal turning relative-URL navigation into a CSRF primitiveAccount-takeover chain: change email via CSRF -> request password reset -> attacker gets it
Common Payloads
<form action="https://target/state-change" method="POST"><input name=email value=attacker@x><input type=submit></form><form method=POST><script>document.forms[0].submit()</script></form><img src="https://target/notify?to=victim&msg=spam">GET /api/graphql/?query=mutation+CreateSnippet(...)POST /api Content-Type: application/x-www-form-urlencoded {"email":"..."} (downgraded from JSON)https://target/oauth/authorize?oauth_token=ATTACKER_TOKEN (no state param)Set state=AAA%00BBB (null-byte to bypass strict equality)POST /logout (no token required)Custom header X-CSRF-Token removed from request entirely; check passes<form action="..." method=GET enctype=text/plain> to bypass form-method allowlist
Detection Strategy
Run a multi-stage probe on every state-changing request observed (POST, PUT, PATCH, DELETE, plus suspicious GETs that look like writes - verbs in path: delete, unsubscribe, trigger, install, send, transfer, set, update, remove, logout, unlock, link, connect).
Replay the request with Origin and Referer rewritten to https://attacker.example. If the server returns the same 2xx/3xx as the baseline, no CSRF check exists.
If a body parameter or header looks like a CSRF token (regex: csrf|xsrf|_token|authenticity|nonce in name, 16+ hex/base64 chars in value), retry with that field deleted. If accepted, the check is opt-in.
With two authenticated sessions, capture the token from session A and replay it inside session B; if accepted, the token is not bound to a session.
Replay POST as GET (and vice-versa) and check whether the alternate verb is accepted without the token.
Replay a JSON body as application/x-www-form-urlencoded and observe if both succeed without separate CSRF protection.
For every Set-Cookie that carries auth state, flag missing SameSite=Lax|Strict.
On /authorize endpoints, flag missing or empty state and check whether the authorize URL is accepted with a fixed state value across two sessions. All stages emit findings with confidence proportional to how many independent signals agree.
Tip: Testing tools that run these checks in parallel across every discovered endpoint can cut the time required substantially compared to fully manual testing, as long as they confirm findings with more than one signal to keep the false-positive rate down.
False-Positive Notes
- Anonymous endpoints (no auth required) are not CSRF-vulnerable by design - the probe must be run with a valid session.
- Pages like /login itself are sometimes intentionally tokenless, so consider Login CSRF a separate finding gated on whether
stateis present. - Some same-origin XHR endpoints rely on custom headers (X-Requested-With, X-CSRF-Token presence as a CORS preflight trigger); these are weak but common - flag at low severity rather than missing-token.
- Endpoints behind WAFs that strip the Origin header on legitimate traffic produce noise; require the response body to match the baseline (not a generic error page) before reporting.
How to Test
Manual Testing Methodology
Here is a systematic approach to identifying Cross-Site Request Forgery vulnerabilities in a target application.
Before testing, map all input vectors that could be affected. Identify parameters, headers, cookies, and request bodies that interact with the vulnerable component. A proxy such as Burp Suite or OWASP ZAP, paired with normal browsing of the target, is usually enough to build this list.
Send a legitimate request and record the normal response: status code, content length, response time, and any identifying tokens. This baseline matters because it's what you'll compare later responses against once payloads are involved.
Inject test payloads into each identified input vector one at a time. Start with benign detection payloads before escalating to anything that could actually trigger the vulnerability. For Cross-Site Request Forgery specifically, reproduce a sensitive state-changing request (password change, email change, funds transfer, privilege grant) as a standalone auto-submitting HTML form or fetch call hosted on a different origin, then load it while authenticated to the target to see whether the action executes.
Compare the response against your baseline, looking specifically for whether the state-changing action actually completed server-side (check the resulting account state, not just the HTTP status code) with no valid anti-CSRF token present, and whether removing or mangling the token, cookie SameSite attribute, or Origin header changes the outcome.
Once a potential vulnerability is detected, confirm it with at least a few independent test cases to rule out coincidence. Document the exact request and response as proof. For Cross-Site Request Forgery, a confirmed finding typically means showing that attacker-controlled input changes the application's behavior in a way that matters for security, not just that a payload was reflected somewhere harmless.
Real-World Impact
Real-World Impact
Cross-site request forgery exploits the trust a web application places in a user's browser. When a victim visits an attacker-controlled page while still authenticated to the target, the attacker can trigger state-changing requests, like password changes or fund transfers, without the victim ever knowing it happened.
The real-world impact depends entirely on what actions the application exposes. CSRF against a password-change endpoint can lead to full account takeover, while CSRF against an admin user-creation endpoint can hand an attacker persistent access to the whole system.
Modern frameworks increasingly ship CSRF protection by default, but it's still commonly bypassed through missing token validation on specific endpoints, token fixation, subdomain cookie scope issues, or CORS misconfigurations that quietly weaken the same-origin policy the protection depends on.
Prevention & Remediation
Prevention and Secure Coding
Preventing Cross-Site Request Forgery takes a defense-in-depth approach — no single control below is sufficient alone, but together they close off both the primary path and the most common bypasses.
Synchronizer tokens. Issue a per-session (or per-request) anti-CSRF token, embed it in every state-changing form or AJAX call, and reject the request server-side if it's missing or doesn't match — this is the strongest single control.
SameSite cookies. Set SameSite=Lax or Strict on session cookies as a strong baseline defense; it doesn't cover every case (some cross-site GETs still leak Lax cookies) but meaningfully shrinks the attack surface with almost no engineering cost.
Double-submit pattern for stateless APIs. Where session state isn't available server-side, compare a token sent as a cookie against the same token sent explicitly in a request header — an attacker's cross-site form can't read or set that header value.
Never trust Referer/Origin alone. Checking the Origin or Referer header is a reasonable supplementary signal but is not sufficient on its own — both can be stripped or spoofed under specific client and network conditions.
Re-authentication for sensitive actions. For actions like changing a password or email, require the current password or a fresh auth challenge — this defeats CSRF entirely for that specific action regardless of token handling elsewhere.
Source reports
A sample of the disclosed HackerOne reports this playbook was synthesized from.