Description
Cross-site request forgery (CSRF) occurs when a malicious website causes a user's browser to send an unwanted request to another site. It abuses credentials that the browser sends automatically, such as cookies, to perform actions the attacker chooses, including modifying or deleting data.
Potential impact
- Data modification: Data may be changed without the user's intent.
- Misuse of user permissions: Account changes or administrative actions available to the victim may be performed.
- Service abuse: An attacker may misuse application features to cause harm.
Remediation
- Use CSRF tokens for state-changing requests.
- Validate request origins on the server, using
Originand related checks to reject untrusted sources. - Do not change state through
GETrequests. Use HTTPS and appropriateSameSitecookie settings alongside CSRF protection.
Examples
Django
The first example assumes there is no CsrfViewMiddleware or other CSRF protection. The absence of a decorator alone does not establish that a view is unprotected.
Before
# Unsafe Django code
from django.http import HttpResponse
def my_view(request):
if request.method == 'POST':
# Process the request without CSRF validation
return HttpResponse("Request processed")
After
# Safe Django code
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
if request.method == 'POST':
# Process the request after validating the CSRF token
return HttpResponse("Request processed")
Explanation:
- Before: Processes POST requests without CSRF validation under the stated configuration.
- After: Uses
@csrf_protectto validate CSRF tokens.
Flask
The Flask example assumes a sufficiently strong secret key is set in the your_secret_key environment variable. Protected forms or AJAX requests must include the CSRF token.
Before
# Unsafe Flask code
from flask import Flask, request
app = Flask(__name__)
@app.route('/submit', methods=['POST'])
def submit():
# Process the request without CSRF validation
return "Request processed"
After
# Safe Flask code
import os
from flask import Flask, request
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv("your_secret_key").encode()
csrf = CSRFProtect(app)
@app.route('/submit', methods=['POST'])
def submit():
# Process the request after validating the CSRF token
return "Request processed"
Explanation:
- Before: Processes POST requests without CSRF validation.
- After: Uses
flask_wtf.csrf.CSRFProtectto validate CSRF tokens.
FastAPI
These examples assume a state-changing endpoint with cookie-based authentication. The second example validates Origin and rejects requests when it is absent or outside the allow-list. Authentication and authorization still need separate implementation.
Before
# Unsafe FastAPI code
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/submit")
async def submit(request: Request):
# Process the request without CSRF validation
return {"message": "Request processed"}
After
# Safe FastAPI code
from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
# Configure trusted hosts
app.add_middleware(
TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"]
)
@app.post("/submit")
async def submit(request: Request):
origin = request.headers.get('origin')
if origin not in ["https://example.com", "https://www.example.com"]:
raise HTTPException(status_code=403, detail="Invalid origin")
# Process the request
return {"message": "Request processed"}
Explanation:
- Before: Processes POST requests without CSRF validation.
- After: The
Origincheck rejects requests from unapproved origins.TrustedHostMiddlewarechecks the destinationHostheader; it does not prevent CSRF on its own.