TL;DR
Cross-Site Request Forgery (CSRF) lets attackers trick users into performing unwanted actions on a web service they’re logged into. This guide shows you how to protect your services using tokens, checking the Origin header, and other methods.
Understanding CSRF
Imagine you’re logged into your online banking. An attacker sends you an email with a link that *looks* harmless. Clicking it might actually send a request to your bank’s server to transfer money – without your knowledge! That’s CSRF in action.
How to Protect Your Web Services
- Use Anti-CSRF Tokens
- Generate a unique, secret token for each user session.
- Include this token as a hidden field in all forms or as a header with sensitive requests (e.g., POST, PUT, DELETE).
- When the request is received, verify that the token matches the one stored for that user’s session. If they don’t match, reject the request.
Example (Python/Flask):
from flask import Flask, render_template, request, session
import secrets
app = Flask(__name__)
app.secret_key = 'your_secret_key'
@app.route('/')
def index():
if 'csrf_token' not in session:
session['csrf_token'] = secrets.token_hex(16) # Generate a random token
return render_template('index.html', csrf_token=session['csrf_token'])
@app.route('/submit', methods=['POST'])
def submit():
if request.form['csrf_token'] == session['csrf_token']:
# Process the form data safely
return 'Form submitted successfully!'
else:
return 'CSRF token invalid!'
- The
Originheader tells you where the request came from. - Verify that the
Originheader matches your expected domain(s). This prevents requests originating from malicious sites. - Be careful with subdomains – decide if they should be allowed or blocked.
Example (Node.js/Express):
const express = require('express');
const app = express();
app.post('/submit', (req, res) => {
const allowedOrigins = ['https://yourdomain.com', 'https://www.yourdomain.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
// Process the request safely
res.send('Request processed successfully!');
} else {
res.status(403).send('Forbidden - Invalid Origin');
}
});
- The
SameSiteattribute controls when cookies are sent with cross-site requests. - Set
SameSite=Strictto only send the cookie on requests originating from your own domain. This offers strong CSRF protection but can break legitimate cross-site functionality. SameSite=Laxis a more lenient option that allows cookies for top-level navigation (e.g., following links) but blocks them for POST requests.
Example (setting in HTTP response header):
Set-Cookie: sessionid=value; SameSite=Strict
- Generate a random value and set it as both a cookie *and* a hidden field in your form.
- On the server, compare the cookie value with the hidden field value. If they match, the request is likely legitimate.
- A WAF can automatically detect and block many common CSRF attacks.
- It’s not a replacement for implementing proper CSRF protection in your code, but it adds an extra layer of security.
Important Notes
- GET requests should never perform state-changing actions. Use POST, PUT, DELETE etc., for those.
- Always validate user input on the server-side. Never trust data received from the client.
- Regularly review your code and dependencies to identify and fix potential vulnerabilities.