Cross Site Request Forgery (CSRF)
ID |
python.cross_site_request_forgery |
Severity |
high |
Resource |
Authentication |
Language |
Python |
Tags |
CWE:352, NIST.SP.800-53, OWASP:2013:A8, PCI-DSS:6.5.1 |
Description
Cross-Site Request Forgery (CSRF) is a security vulnerability that occurs when a malicious actor tricks a user’s browser into performing unwanted actions on a trusted web application where the user is authenticated. It primarily exploits the trust that a web application has in an authenticated user’s browser.
It can lead to unauthorized actions being executed in a web application on behalf of the user, potentially compromising personal data, making unauthorized transactions, or performing administrative operations.
Rationale
CSRF exploits the trust that a web application has in the user’s browser. Essentially, the attacker crafts a request that the user’s browser sends to a web server where the user is authenticated.
The key vulnerability is the application’s inability to verify that the request originated from its own legitimate web pages and intentionally made by the user, rather than from a malicious site. This is why CSRF protections typically involve adding unique tokens to forms that the server can verify came from its own pages.
If the vulnerable application does not have the necessary protection, the attacker can execute arbitrary actions on the user’s behalf.
In Python web frameworks (such as Flask), forms and state-changing requests are vulnerable to CSRF unless adequately protected. For example, a form submission without a CSRF token can be exploited:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/submit', methods=['POST'])
def submit():
# Vulnerable to CSRF
user_data = request.form['data']
process_user_data(user_data)
return 'Data submitted successfully!'
Remediation
The most common protection strategy against CSRF is to use anti-CSRF tokens, to ensure that requests are originated from the legitimate user. The token should be generated on the server backend, and included in every form or request to the backend.
Additional protection techniques that do not seclude the need for anti-CSRF token:
-
Validate Referer Header: In some cases, checking the
Referer
orOrigin
headers can provide another layer of protection, ensuring that the request originates from the same site. -
Force User Interaction: Force significant actions to require additional confirmation steps, like re-authentication or other forms of verification.
-
SameSite Cookie Attribute: Use the
SameSite
attribute for cookies to prevent them from being sent in cross-site requests.
Implementing these strategies in your applications will significantly reduce the risk of CSRF attacks.
Django
Django provides built-in CSRF protection. By default, CSRF tokens are included in forms when using django.middleware.csrf.CsrfViewMiddleware
into your settings MIDDLEWARE_CLASSES
.
from django.shortcuts import render
from django import forms
class DataForm(forms.Form):
data = forms.CharField(label='Data', max_length=100)
def submit(request):
if request.method == 'POST':
form = DataForm(request.POST)
if form.is_valid():
user_data = form.cleaned_data['data']
process_user_data(user_data)
return render(request, 'success.html')
else:
form = DataForm()
return render(request, 'submit.html', {'form': form})
In your Django template, ensure the CSRF token is included:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
Flask
To safeguard Flask web applications against CSRF, they should employ CSRF tokens in forms that perform state-changing actions. Tools like Flask-WTF help automate this process by managing CSRF token creation and verification.
from flask import Flask, render_template, request, flash
from flask_wtf import FlaskForm, CSRFProtect
from wtforms import StringField
app = Flask(__name__)
app.secret_key = 'your_secret_key'
csrf = CSRFProtect(app)
class DataForm(FlaskForm):
data = StringField('Data')
@app.route('/submit', methods=['GET', 'POST'])
def submit():
form = DataForm()
if form.validate_on_submit():
user_data = form.data.data
process_user_data(user_data)
flash('Data submitted successfully!', 'success')
return render_template('submit.html', form=form)
In your HTML forms, include the CSRF token with:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.data.label }}
{{ form.data() }}
<input type="submit" value="Submit">
</form>
FastAPI
The fastapi_csrf_protect
library can be used to implement CSRF protection in FastAPI applications. Below is an example setup.
from fastapi import FastAPI, Request, Form, Response, Depends
from fastapi.responses import HTMLResponse
from fastapi_csrf_protect import CsrfProtect, CsrfProtectSettings
from pydantic import BaseModel
app = FastAPI()
class CsrfSettings(BaseModel, CsrfProtectSettings):
secret_key: str = "a_very_secret_key"
@CsrfProtect.load_config
def get_csrf_config():
return CsrfSettings()
@app.get("/", response_class=HTMLResponse)
async def form_get(csrf_protect: CsrfProtect = Depends()):
csrf_token = csrf_protect.generate_csrf()
html_content = f"""
<form method="post">
<input type="hidden" name="csrf_token" value="{csrf_token}">
<input name="data" type="text">
<input type="submit" value="Submit">
</form>
"""
return HTMLResponse(content=html_content)
@app.post("/")
async def form_post(data: str = Form(...), csrf_protect: CsrfProtect = Depends()):
csrf_protect.validate_csrf(csrf_protect.extract_csrf())
process_user_data(data)
return Response(content="Data submitted successfully!")
By implementing CSRF tokens, the application ensures that the request originates from a legitimate source within the user’s session, thus mitigating the risk of CSRF exploits.
References
-
CWE-352 : Cross-Site Request Forgery (CSRF)
-
OWASP - Top 10 2021 Category A01 : Broken Access Control.
-
Cross-Site Request Forgery Prevention Cheat Sheet, in OWASP Cheat Sheet Series.