CSRF Testing: Cross-Site Request Forgery Detection and Prevention
A banking application lets customers transfer money via a POST request. The request contains the destination account and amount — but it relies entirely on the session cookie for authentication and includes no other secret. An attacker builds an invisible 1×1 pixel <img> tag on a malicious page that triggers a GET request to the bank's funds transfer endpoint. Any logged-in customer who visits the attacker's page sends a funds transfer they didn't initiate. The browser helpfully includes the session cookie with every request to that domain — that's how cookies work.
Cross-Site Request Forgery (CSRF) exploits the browser's same-origin policy in reverse: while JavaScript from a different origin can't read the response, the browser will still send the request — including all cookies — without any indication to the user. This guide covers how to test for CSRF systematically, how to validate that CSRF protections actually work, and where modern defenses can still fall short.
What CSRF Actually Requires
A CSRF vulnerability requires three conditions to be exploitable:
- A state-changing action — something that modifies data, sends communications, or changes account settings. Read-only endpoints aren't interesting for CSRF.
- Cookie-based session authentication — the application relies on cookies (or other credentials the browser automatically sends) to identify the user, not a custom header like
Authorization: Bearer. - No unpredictable parameters — the attacker must be able to construct a request that will be accepted, which means no CSRF token, no secret value derived from the user's session, and no verification of origin.
CSRF does not require the attacker to steal the victim's cookie. The attacker never sees the cookie — they just need the victim's browser to make the request while the victim is authenticated, which the browser does automatically.
Identifying Attack Surface: State-Changing Requests
CSRF testing starts with mapping the application's state-changing functionality. Walk through the application as an authenticated user and capture all requests in Burp Suite. Then filter for anything that modifies state:
- Account settings changes: email, password, 2FA, notification preferences
- Payment and financial actions: fund transfers, payment method changes, subscription upgrades
- User management: privilege changes, user creation, account deletion
- Data operations: create, update, delete operations on application objects
- Administrative actions: configuration changes, user impersonation
- Security-relevant actions: API key generation, OAuth application authorization
Note that CSRF can affect GET requests too, not just POST. If a GET request causes a state change (bad practice but common in legacy applications), it's actually easier to exploit — an <img src="..."> tag is sufficient. Test all state-changing requests regardless of HTTP method.
Testing CSRF Token Validation
If the application includes a CSRF token in forms or API requests, you need to test whether the validation is actually robust. Common weaknesses:
Completely Absent Validation
Remove the CSRF token parameter entirely from the request and replay it. If the action succeeds without the token, there's no CSRF protection at all — the token is present but not validated.
Empty Token Accepted
Set the CSRF token to an empty string: csrf_token=. Some implementations skip validation when the token is blank, assuming "no token = form doesn't have this feature."
Static or Predictable Tokens
Log out and log in again, comparing the CSRF token across sessions. Log in as two different users and compare tokens. If tokens are the same across sessions or follow a predictable pattern, they're not providing protection — the attacker can use a known valid token from their own session in the attack payload.
Token Not Tied to Session
Capture a CSRF token from your attacker-controlled account. Then craft a CSRF attack that uses your own valid token in a request targeting the victim. If the application only checks that the token exists in its database without verifying it belongs to the current session, this attack works.
Token in URL Only
If the CSRF token appears in the URL rather than a form field or header, it may be logged in server access logs, referer headers, and browser history — exposing it to attackers through those channels. Test whether the token in the URL can be used from a different browser session.
Testing SameSite Cookie Attribute
Modern browsers have adopted the SameSite cookie attribute as a CSRF defense at the browser level. Understanding how to test it requires knowing what each value actually does:
| SameSite Value | CSRF Protection | Limitations |
|---|---|---|
Strict |
Strong — cookie never sent on cross-site requests | Breaks OAuth flows, email link sign-ins, external redirects |
Lax |
Moderate — cookie sent on top-level navigation GET only | Doesn't protect GET-based state-changing actions; 2-minute Chrome leniency window |
None |
None — cookie sent on all cross-site requests | Requires Secure flag; full CSRF exposure |
| Not set (pre-Chrome 80) | None — behaves like None | Full CSRF exposure in older browsers |
| Not set (Chrome 80+) | Lax by default | Same limitations as Lax; 2-minute new-cookie leniency |
To check the SameSite attribute of session cookies: in Burp Suite, examine the Set-Cookie header in responses that set the session cookie. Look for SameSite=Lax, SameSite=Strict, or SameSite=None. If SameSite is absent, the browser defaults to Lax in Chrome 80+ but remains None in older browsers.
Chrome's 2-Minute Leniency Window
Chrome implements a 2-minute window where newly-set cookies without a SameSite attribute behave as None rather than Lax. This was intended to prevent session login flows from breaking. For CSRF testing, it means that if an attacker can force the victim to log in (for example, via a login CSRF attack that logs them into the attacker's account), the 2-minute window allows cross-site requests with the new session cookie.
Referer Header Validation Weaknesses
Some applications validate the Referer header as a CSRF defense. This is a valid secondary control but has testable weaknesses:
Missing Referer Accepted
Remove the Referer header entirely from the request (use Burp's "Remove header" match-and-replace rule). Some applications only validate the Referer when present — they don't reject requests with no Referer header, which means attacks from HTTPS pages that suppress the Referer (using <meta name="referrer" content="no-referrer">) succeed.
Weak Referer Matching
Some applications check whether the target domain appears anywhere in the Referer, not whether the Referer starts with the expected origin. This is exploitable by using a domain like https://evil.com/victim-domain.com. Test by sending a request with:
Referer: https://attacker.com?victim.example.com
If the application accepts this, the validation is substring-based and bypassable.
JSON-Based API CSRF
APIs that use JSON request bodies are commonly assumed to be CSRF-safe because form submissions can't set Content-Type: application/json from a cross-origin page. This assumption has exceptions:
Flash (now extinct but relevant for older applications) could send arbitrary content types. More relevant today: if the API accepts text/plain content type and parses the body as JSON anyway, a form submission can trigger the request with the correct payload. Test by replaying JSON API requests with Content-Type: text/plain — if they're processed, the "JSON = CSRF safe" assumption is wrong.
Additionally, some APIs accept both JSON and form-encoded parameters, and the form-encoded versions are exploitable via standard CSRF even if the JSON versions are not. Test both formats against every endpoint.
Building a CSRF Proof-of-Concept
For a confirmed CSRF finding, build a working proof-of-concept to demonstrate impact. Burp Suite Pro can generate this automatically: right-click any captured request → "Generate CSRF PoC" → "Test in browser". For manual construction:
<!-- Standard POST form CSRF PoC -->
<html>
<body onload="document.forms[0].submit()">
<form action="https://victim.example.com/account/email" method="POST">
<input type="hidden" name="email" value="attacker@evil.com">
<input type="hidden" name="confirm_email" value="attacker@evil.com">
</form>
</body>
</html>
<!-- GET-based CSRF (simpler) -->
<img src="https://victim.example.com/transfer?to=attacker&amount=1000"
width="1" height="1" border="0">
<!-- JSON CSRF via text/plain (when applicable) -->
<html>
<body onload="document.forms[0].submit()">
<form action="https://victim.example.com/api/settings"
method="POST"
enctype="text/plain">
<input type="hidden" name='{"action":"delete","confirm":true,"x":"' value='"}'>
</form>
</body>
</html>
Include the working PoC in your report. Stakeholders who haven't seen CSRF demonstrated often underestimate severity — a working PoC that changes an email address or initiates a payment makes the risk concrete.
Remediation: Defense in Depth
CSRF defense is most reliable with multiple layers. In priority order:
- CSRF tokens — generate a per-session (or per-request) cryptographically random token, include it in forms and AJAX headers, validate server-side. The token must be tied to the user's session and unpredictable.
- SameSite=Lax or Strict on session cookies — set this for all authentication cookies as a defense-in-depth layer.
Laxis generally safe for most applications;Strictif OAuth flows and external links don't need to preserve login state. - Check the Origin or Referer header — validate that state-changing requests originate from the expected domain. This is a secondary layer; don't rely on it alone.
- Require re-authentication for sensitive actions — funds transfers, password changes, account deletion should require the user to enter their password, which an attacker cannot forge even with a valid CSRF vector.
What doesn't work as a CSRF defense:
- HTTPS alone (CSRF works over HTTPS)
- POST-only endpoints (POST forms can be submitted cross-site)
- Secret cookies that the attacker can't read (the cookie is sent automatically by the browser, the attacker doesn't need to read it)
- Checking the User-Agent header (trivially forged)
Ironimo tests for CSRF vulnerabilities across your web application's state-changing endpoints — including token validation weaknesses, SameSite cookie attribute misconfigurations, missing Referer validation, and GET-based state changes. Every confirmed finding includes a working proof-of-concept you can hand directly to your development team.
Start free scan