The one-line difference: CSRF tricks a user’s browser into sending a request the user didn’t intend; SSRF tricks your server into sending a request the attacker chose.
They both have “request forgery” in the name, so people lump them together. But they’re completely different beasts that’ll bite you in completely different ways, and the defense for one does nothing for the other. I’ve spent way too many nights debugging both, so here’s the side-by-side first, then the detail.
CSRF vs SSRF side by side
| CSRF (Cross-Site Request Forgery) | SSRF (Server-Side Request Forgery) | |
|---|---|---|
| Who sends the forged request | The victim’s browser | Your application server |
| Whose trust is abused | The site’s trust in the user’s cookies | The network’s trust in the server |
| What the attacker needs | A logged-in victim to visit their page | Any input the server turns into a URL fetch |
| Typical target | A state-changing endpoint (POST /transfer, /email/change) | Internal hosts, cloud metadata (169.254.169.254), localhost admin ports |
| Attacker sees the response? | No, the browser’s same-origin policy hides it | Sometimes (full-read SSRF); often blind |
| Worst case | Actions taken as the victim: account takeover, transfers | Cloud credential theft, internal network access, sometimes RCE |
| Primary defenses | CSRF tokens, SameSite cookies, Origin checks | Destination allowlist, resolved-IP checks, egress filtering, IMDSv2 |
| CWE | CWE-352 | CWE-918 |
| OWASP Top 10 | Under A01 Broken Access Control | A10:2021; merged into A01 in the 2025 list |

Go deeper: the CSRF guide covers SameSite and token bypasses; the SSRF guide covers filter bypasses and cloud metadata attacks.
What is CSRF (Cross-Site Request Forgery)?
CSRF attacks exploit the trust that a website has in a user’s browser. When a user is authenticated to a website, their browser automatically includes session cookies with every request. Attackers exploit this by tricking users into making unintended requests to the target site.
How CSRF Attacks Work
- User logs into a legitimate website (bank.com)
- Browser stores authentication cookies
- User visits a malicious website while still logged in
- Malicious site triggers a request to bank.com
- Browser automatically includes authentication cookies
- Bank processes the request as if user intended it
Real CSRF Attack Example
Vulnerable Transfer Endpoint:
// Vulnerable Node.js endpoint
app.post('/transfer', (req, res) => {
// No CSRF protection!
const { amount, toAccount } = req.body;
const userId = req.session.userId; // From session cookie
transferMoney(userId, toAccount, amount);
res.json({success: true});
});
Malicious Website Code:
<!-- Attacker's website -->
<form id="attack" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="toAccount" value="attacker-account">
</form>
<script>
document.getElementById('attack').submit(); // Auto-submit
</script>
When the victim visits the malicious site while logged into the bank, $10,000 gets transferred automatically!
What is SSRF (Server-Side Request Forgery)?
SSRF attacks exploit the trust that external services have in your application server. Attackers manipulate your server into making requests to unintended destinations, often accessing internal resources that should be protected.
How SSRF Attacks Work
- Application accepts user-provided URL
- Server makes request to that URL
- Attacker provides internal/malicious URL
- Server requests internal resources
- Attacker gains access to sensitive data
Real SSRF Attack Example
Vulnerable Image Resize Endpoint:
# Vulnerable Python Flask endpoint
@app.route('/resize-image', methods=['POST'])
def resize_image():
image_url = request.form.get('url')
# No validation - DANGEROUS!
response = requests.get(image_url)
# Process image...
return process_image(response.content)
SSRF Attack Payloads:
# Access AWS metadata (steal credentials)
POST /resize-image
url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Access internal admin panel
POST /resize-image
url=http://192.168.1.100:8080/admin
# Read local files
POST /resize-image
url=file:///etc/passwd
Key Differences: Impact and Scope
CSRF Impact
- Account takeover - Change passwords, email addresses
- Unauthorized transactions - Money transfers, purchases
- Data modification - Update profiles, post content
- Privilege escalation - Add admin users
SSRF Impact
- Internal network access - Discover and access internal services
- Cloud metadata theft - Steal AWS/GCP/Azure credentials
- File system access - Read sensitive configuration files
- Port scanning - Map internal network infrastructure
Prevention Strategies
CSRF Prevention
1. CSRF Tokens (Primary Defense)
// Express.js with csurf middleware
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
app.get('/transfer', (req, res) => {
res.render('transfer', { csrfToken: req.csrfToken() });
});
app.post('/transfer', (req, res) => {
// Token automatically validated by middleware
transferMoney(req.session.userId, req.body.toAccount, req.body.amount);
});
<!-- HTML form with CSRF token -->
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="number" name="amount" required>
<input type="text" name="toAccount" required>
<button type="submit">Transfer</button>
</form>
2. SameSite Cookie Attribute
app.use(session({
secret: 'your-secret',
cookie: {
sameSite: 'strict', // or 'lax'
secure: true, // HTTPS only
httpOnly: true // Prevent XSS
}
}));
3. Origin/Referer Header Validation
function validateOrigin(req, res, next) {
const origin = req.get('Origin') || req.get('Referer');
const allowedOrigins = ['https://yourdomain.com'];
if (!allowedOrigins.some(allowed => origin && origin.startsWith(allowed))) {
return res.status(403).json({error: 'Invalid origin'});
}
next();
}
SSRF Prevention
1. Input Validation and Allowlisting
# Python example with comprehensive validation
import re
from urllib.parse import urlparse
from ipaddress import ip_address, AddressValueError
def is_safe_url(url):
try:
parsed = urlparse(url)
# Only allow HTTP/HTTPS
if parsed.scheme not in ['http', 'https']:
return False
# Block private IP ranges
try:
ip = ip_address(parsed.hostname)
if ip.is_private or ip.is_loopback:
return False
except (AddressValueError, TypeError):
pass
# Allowlist domains only
allowed_domains = ['cdn.trusted.com', 'api.partner.com']
if parsed.hostname not in allowed_domains:
return False
return True
except:
return False
@app.route('/fetch-image', methods=['POST'])
def fetch_image():
url = request.form.get('url')
if not is_safe_url(url):
return jsonify({'error': 'Invalid URL'}), 400
response = requests.get(url, timeout=5)
return process_image(response.content)
2. Network Segmentation
# Firewall rules to block SSRF
iptables -A OUTPUT -d 169.254.169.254 -j DROP # AWS metadata
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP # Private networks
iptables -A OUTPUT -d 172.16.0.0/12 -j DROP
iptables -A OUTPUT -d 192.168.0.0/16 -j DROP
iptables -A OUTPUT -d 127.0.0.0/8 -j DROP # Localhost
๐ฏ Practice the SSRF half: The SSRF Target Simulator covers the server-side case hands-on โ reach a simulated metadata endpoint past a filter you control. No real requests are made.
Testing for Both Vulnerabilities
CSRF Testing
# Test with curl (should fail without token)
curl -X POST https://target.com/transfer \
-d "amount=1000&toAccount=attacker" \
-b "session=valid_session_cookie"
# Test SameSite protection
curl -X POST https://target.com/transfer \
-H "Origin: https://evil.com" \
-d "amount=1000&toAccount=attacker"
SSRF Testing
# Test internal network access
curl -X POST https://target.com/fetch-image \
-d "url=http://127.0.0.1:22"
# Test metadata access
curl -X POST https://target.com/fetch-image \
-d "url=http://169.254.169.254/latest/meta-data/"
# Test file access
curl -X POST https://target.com/fetch-image \
-d "url=file:///etc/passwd"
Framework-Specific Protection
Django (Python)
# CSRF protection (built-in)
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]
# Template
{% csrf_token %}
# SSRF protection
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
def safe_fetch(url):
validator = URLValidator()
try:
validator(url)
# Add additional SSRF checks
return requests.get(url)
except ValidationError:
raise ValueError("Invalid URL")
Spring Boot (Java)
// CSRF protection
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
}
// SSRF protection
@Service
public class SafeHttpService {
private static final Set<String> ALLOWED_HOSTS = Set.of("api.trusted.com");
public String safeFetch(String url) {
try {
URL parsedUrl = new URL(url);
if (!ALLOWED_HOSTS.contains(parsedUrl.getHost())) {
throw new IllegalArgumentException("Host not allowed");
}
return restTemplate.getForObject(url, String.class);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL");
}
}
}
Advanced Attack Scenarios
CSRF: JSON API Attacks
Modern SPAs using JSON APIs are also vulnerable:
<!-- Attacker site -->
<script>
fetch('https://api.target.com/user/update', {
method: 'POST',
credentials: 'include', // Include cookies
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: 'attacker@evil.com'})
});
</script>
Defense: Custom request headers + CORS preflight:
// Require custom header
app.use((req, res, next) => {
if (req.method === 'POST' && !req.get('X-Requested-With')) {
return res.status(403).json({error: 'Missing custom header'});
}
next();
});
SSRF: DNS Rebinding Attacks
Attackers can bypass IP filtering using DNS:
- Create domain pointing to public IP
- After validation, change DNS to private IP
- Server resolves to internal network
Defense: Re-resolve DNS before making request:
def safe_request(url):
# Validate initially
if not is_safe_url(url):
return None
# Re-resolve before request
time.sleep(1) # Allow DNS changes
if not is_safe_url(url):
return None
return requests.get(url)
Security Checklist
CSRF Prevention Checklist
- CSRF tokens implemented for state-changing requests
- SameSite cookies configured (Strict/Lax)
- Origin/Referer header validation
- Custom headers for API requests
- Proper CORS configuration
SSRF Prevention Checklist
- URL allowlisting implemented
- Private IP ranges blocked
- Network segmentation in place
- Metadata services blocked (169.254.169.254)
- Request timeouts configured
- Response size limits enforced
Conclusion
CSRF and SSRF represent fundamentally different attack vectors that require distinct defense strategies:
- CSRF targets user trust through browser sessions - defend with tokens and cookie policies
- SSRF targets server trust through request manipulation - defend with validation and network controls
Understanding both vulnerabilities is essential for building secure web applications. Implement proper defenses during development rather than trying to retrofit security later.
Remember: security is a process, not a destination. Regularly test your applications, stay updated on new attack techniques, and maintain robust logging to detect potential attacks.
Related Security Topics
๐ SSRF Deep Dive Series:
- 7 Critical SSRF Attack Techniques - Master SSRF attack methods
- SSRF Prevention Guide - Complete defense strategies
- CVE-2026-27696 Analysis - Real-world SSRF bypass
- CSRF vs SSRF Guide - Compare both vulnerabilities (you are here)
๐ฏ Web Security Fundamentals
- Content Security Policy Guide - Prevent XSS attacks with CSP headers
- Python Security Guide - Secure coding in Python
- Common Weakness Enumeration - CWE-352 (CSRF) and CWE-918 (SSRF)
๐ ๏ธ Hands-On Practice
- Security Playground - Interactive CSRF and SSRF demos
- CSP Toolkit - Test and analyze security headers
๐ Python Developers
- Python Requests Security - Secure HTTP client usage
- Secure Python Applications - Comprehensive vulnerability prevention