scanrub
Cross-Site Request Forgery (CSRF)high prioritypartial coverage

Cross-Site Request Forgery

5 min read 235 reports analyzed ScanRub Research
Share
Live Playground · Powered by this research
CSRF Token Strength

Paste a CSRF token value to check its length, entropy, and predictability.

Payloads from this research (10 total)
<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(...)
These payloads were synthesized from real HackerOne disclosures. Click any payload to copy it, then paste it into the tester above to see how our detection classifies it.

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 endpoints
  • OAuth/SSO authorize URLs missing state parameter (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 change
  • GraphQL mutation moved from POST to GET to avoid X-CSRF-Token header
  • OAuth flow without state param: attacker links victim account to attacker IdP
  • Null byte / whitespace injection in state parameter to bypass strict-equality checks
  • Web cache deception (CloudFlare) to capture victim's CSRF token from cached page
  • Flash 307-redirect trick to land cross-origin POST with custom Content-Type
  • Same 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 primitive
  • Account-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).

Stage 1: origin/referer probe

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.

Stage 2: token-strip

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.

Stage 3: token-replay

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.

Stage 4: method-confusion

Replay POST as GET (and vice-versa) and check whether the alternate verb is accepted without the token.

Stage 5: Content-Type confusion

Replay a JSON body as application/x-www-form-urlencoded and observe if both succeed without separate CSRF protection.

Stage 6: cookie audit

For every Set-Cookie that carries auth state, flag missing SameSite=Lax|Strict.

Stage 7: OAuth probe

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 state is 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.

Step 1: Reconnaissance and Surface Mapping

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.

Step 2: Baseline Request

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.

Step 3: Payload Injection

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.

Step 4: Response Analysis

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.

Step 5: Confirmation

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.

Frequently Asked Questions

What is Cross-Site Request Forgery?
CSRF reports in this corpus cluster around four root causes: (1) missing or unvalidated anti-CSRF tokens on state-changing endpoints, (2) state-changing actions exposed via GET, (3) cookies without SameSite=Lax/Strict so cross-origin POSTs carry session, and (4) custom CSRF token schemes broken in subtle ways (token reused across users, server only validates token on POST so GET-method-bypass works, token validated only when present, JSON->form Content-Type confusion).
How common is Cross-Site Request Forgery in bug bounty reports?
Scanrub's research corpus for this playbook is built from 235 disclosed HackerOne reports in this category, synthesized for detection and prevention guidance rather than reproduced verbatim.
How do I test for Cross-Site Request Forgery?
Map every input vector, record a baseline response, then inject targeted test payloads one field at a time and compare the response for timing, length, error, or reflection differences from that baseline. The "How to Test" section above walks through the full methodology for this specific vulnerability class.
What is the single most effective fix for Cross-Site Request Forgery?
Issue a per-session anti-CSRF token on every state-changing request and validate it server-side, backed by SameSite=Lax/Strict cookies as a baseline.
Weekly security research

New vulnerability playbooks, tool updates, and bug bounty insights - delivered to your inbox. No spam.

Unsubscribe anytime. We respect your inbox.
Press ⌘K to search×