Cross Site Request Forgery in Django
How Cross Site Request Forgery Manifests in Django
Cross Site Request Forgery (CSRF) in Django occurs when an attacker tricks an authenticated user into submitting a malicious request without their knowledge. In Django applications, this typically manifests through state-changing operations like POST, PUT, DELETE requests that lack proper CSRF protection.
The most common Django CSRF vulnerability appears in form submissions. Consider this vulnerable view:
from django.shortcuts import render, redirect
from django.http import HttpResponse
def vulnerable_view(request):
if request.method == 'POST':
# Process form data without CSRF check
user = request.user
user.email = request.POST['email']
user.save()
return redirect('profile')
return render(request, 'form.html')An attacker can create a malicious page that submits this form automatically:
<form action="https://yourdjangoapp.com/profile/update" method="POST" id="csrf-form" style="display:none;">
<input type="hidden" name="email" value="[email protected]">
</form>
<script>
document.getElementById('csrf-form').submit();
</script>When an authenticated user visits this malicious page, their browser automatically includes their session cookie, allowing the attacker to change their email without consent.
Django's @csrf_protect decorator and {% csrf_token %} template tag prevent this by requiring a unique token that attackers cannot forge. Without these protections, any authenticated POST request becomes vulnerable.
Django-Specific Detection
Detecting CSRF vulnerabilities in Django requires examining both code patterns and runtime behavior. middleBrick's Django-specific scanning identifies CSRF issues through several methods:
Code Analysis - middleBrick analyzes your Django templates and views for missing CSRF tokens. It flags forms that lack {% csrf_token %} in POST forms and views decorated with @csrf_exempt or missing @csrf_protect.
Runtime Testing - The scanner sends POST requests without CSRF tokens to test if the application accepts them. Django's CsrfViewMiddleware should reject these requests with HTTP 403 Forbidden, but misconfigurations can allow them through.
Middleware Configuration - middleBrick checks your MIDDLEWARE settings for proper CsrfViewMiddleware ordering. The middleware must be placed after SessionMiddleware and AuthenticationMiddleware to function correctly.
Decorator Usage - The scanner identifies @csrf_exempt usage throughout your codebase, flagging potentially dangerous exemptions. It also checks for proper @csrf_protect usage on state-changing views.
CSRF Cookie Settings - middleBrick verifies your CSRF_COOKIE settings, checking for secure cookie configurations and proper SameSite attributes.
Using middleBrick's CLI for Django CSRF detection:
npx middlebrick scan https://yourdjangoapp.com --output=jsonThe scanner reports findings with severity levels and specific line numbers where CSRF protections are missing, helping developers quickly locate and fix vulnerabilities.
Django-Specific Remediation
Remediating CSRF vulnerabilities in Django involves implementing proper protections at both the template and view levels. Here's how to secure your Django application:
Template Protection - Always include {% csrf_token %} in POST forms:
<form method="post" action="{% url 'update_profile' %}">
{% csrf_token %}
<input type="email" name="email" required>
<button type="submit">Update</button>
</form>View Protection - Use @csrf_protect decorator on state-changing views:
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def update_profile(request):
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
form.save()
return redirect('profile')
else:
form = ProfileForm()
return render(request, 'update.html', {'form': form})Class-Based Views - For CBVs, use the csrf_protect mixin or ensure middleware handles protection:
from django.views.generic.edit import UpdateView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
@method_decorator(csrf_protect, name='dispatch')
class ProfileUpdateView(UpdateView):
model = UserProfile
fields = ['email', 'name']
template_name = 'update.html'API Endpoints - For AJAX requests, ensure CSRF tokens are included in headers:
// In your template
{% csrf_token %}
<script>
var csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/profile', {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json',
},
body: JSON.stringify({email: '[email protected]'})
});
</script>Middleware Configuration - Ensure proper middleware ordering in settings.py:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# ... other middleware
]Testing Protection - Use Django's test client to verify CSRF protection:
from django.test import TestCase, Client
from django.urls import reverse
class CsrfTest(TestCase):
def test_csrf_protection(self):
client = Client()
response = client.post(reverse('update_profile'), {
'email': '[email protected]'
})
self.assertEqual(response.status_code, 403) # Should be forbidden without CSRF token