CSRF Remediation
Cross-Site Request Forgery (CSRF) tricks authenticated users into executing unwanted actions. Proper token validation and cookie configurations provide robust protection.
Understanding CSRF
Attack Scenario
- User logs into bank.com and receives session cookie
- User visits attacker's site while still logged in
- Attacker's page contains hidden form targeting bank.com
- Form auto-submits, browser includes session cookie
- Bank processes request as legitimate user action
CSRF Token Implementation
Best Practice
Token Generation
# Python/Flask - Generate secure CSRF token
import secrets
def generate_csrf_token():
"""Generate a cryptographically secure CSRF token"""
token = secrets.token_hex(32) # 64 character hex string
session['csrf_token'] = token
return token
# In template
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">Token Validation
# Python/Flask - Validate CSRF token
from functools import wraps
from flask import request, session, abort
def csrf_protect(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if request.method in ['POST', 'PUT', 'DELETE', 'PATCH']:
token = request.form.get('csrf_token') or request.headers.get('X-CSRF-Token')
if not token or token != session.get('csrf_token'):
abort(403, 'CSRF token invalid or missing')
return f(*args, **kwargs)
return decorated_function
@app.route('/transfer', methods=['POST'])
@csrf_protect
def transfer():
# Process transfer
passExpress.js Implementation
// Node.js/Express with csurf middleware
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(csrf({ cookie: true }));
// Make token available to templates
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
// In template (EJS/Pug)
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
// For AJAX requests
<meta name="csrf-token" content="<%= csrfToken %>">
// JavaScript
fetch('/api/transfer', {
method: 'POST',
headers: {
'CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});SameSite Cookie Attribute
The SameSite attribute prevents the browser from sending cookies with cross-site requests, providing defense-in-depth against CSRF.
Strict
Cookie only sent in first-party context. Best CSRF protection but may break legitimate flows.
Lax (Recommended)
Cookie sent with top-level navigations and GET from external sites. Good balance.
None
Cookie sent in all contexts. Requires Secure flag. No CSRF protection.
# Python/Flask - Set SameSite cookie
response.set_cookie(
'session',
value=session_id,
httponly=True,
secure=True, # Required for SameSite=None
samesite='Lax' # or 'Strict'
)
# Node.js/Express
res.cookie('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax'
});
# PHP
setcookie('session', $sessionId, [
'httponly' => true,
'secure' => true,
'samesite' => 'Lax'
]);Framework-Specific Solutions
Django
# settings.py - CSRF is enabled by default
CSRF_COOKIE_SECURE = True # Production only
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = 'Lax'
# In templates
{% csrf_token %}
# For AJAX (include in headers)
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/', {
headers: {'X-CSRFToken': csrftoken}
});Spring Boot
// SecurityConfig.java - CSRF enabled by default
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
// ... other config
}
}
// In Thymeleaf templates
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>ASP.NET Core
// Startup.cs - Add antiforgery
services.AddAntiforgery(options => {
options.HeaderName = "X-CSRF-TOKEN";
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
});
// In Razor views
@Html.AntiForgeryToken()
// In controller
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult Transfer(TransferModel model) { }Common Mistakes to Avoid
- โ Token in URL: Never pass CSRF tokens in query strings (logged, cached)
- โ GET for state changes: Use POST/PUT/DELETE for actions that modify data
- โ Predictable tokens: Use cryptographically random tokens, not user IDs or timestamps
- โ Relying only on Referer: Referer header can be spoofed or stripped
- โ Skipping AJAX: Protect API endpoints too, not just form submissions
๐งช Testing Verification
CSRF Token Tests
# Test 1: Missing token should fail
curl -X POST https://site.com/transfer \
-d "amount=100&to=attacker" \
-b "session=valid_session_cookie"
# Expected: 403 Forbidden
# Test 2: Invalid token should fail
curl -X POST https://site.com/transfer \
-d "amount=100&to=attacker&csrf_token=invalid123" \
-b "session=valid_session_cookie"
# Expected: 403 Forbidden
# Test 3: Token reuse should fail (if using one-time tokens)
# Use same token twice - second request should failSameSite Cookie Test
# Check SameSite attribute on session cookie
curl -I https://yoursite.com/login -c - | grep -i "set-cookie"
# Expected: SameSite=Lax or SameSite=Strict in cookie headerโ ๏ธ Common Mistakes
โ Token in GET Parameters
<!-- DON'T: Token leaked in URLs/logs -->
<a href="/delete?csrf=abc123">Delete</a>Tokens in URLs get logged and leaked via Referer
โ Correct Approach
<!-- DO: Token in hidden form field -->
<form method="POST">
<input type="hidden" name="csrf" value="...">
</form>POST body is not logged or in Referer
โ Weak Token Validation
# DON'T: Only check if token exists
if request.form.get('csrf_token'):
process_request()Any token value would be accepted
โ Correct Approach
# DO: Compare tokens securely
import hmac
if hmac.compare_digest(
request.form['csrf'], session['csrf']):
process_request()Timing-safe comparison prevents attacks
Verification Checklist
- โ All state-changing requests require valid CSRF token
- โ Tokens are cryptographically random (min 128 bits)
- โ Tokens are validated server-side on every request
- โ SameSite cookie attribute set to Lax or Strict
- โ Tokens are rotated after login
- โ AJAX requests include CSRF header
- โ Framework CSRF middleware is enabled
- โ No state changes via GET requests