CSRF Remediation

Risk Severity
๐ŸŸ  High
Fix Effort
โšก Low (Quick Fix)
Est. Time
โฑ๏ธ 1-2 hours
Reference
A01:2021 CWE-352

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

  1. User logs into bank.com and receives session cookie
  2. User visits attacker's site while still logged in
  3. Attacker's page contains hidden form targeting bank.com
  4. Form auto-submits, browser includes session cookie
  5. Bank processes request as legitimate user action

CSRF Token Implementation

Best Practice

Use framework-provided CSRF protection when available. Most modern frameworks include robust CSRF middleware that handles token generation, validation, and rotation.

Token Generation

python
# 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
# 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
    pass

Express.js Implementation

javascript
// 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
# 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

python
# 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

java
// 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

csharp
// 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

bash
# 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 fail

SameSite Cookie Test

bash
# 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

html
<!-- 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

html
<!-- 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

python
# DON'T: Only check if token exists
if request.form.get('csrf_token'):
    process_request()

Any token value would be accepted

โœ… Correct Approach

python
# 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