Session Fixation in Django
How Session Fixation Manifests in Django
Session fixation in Django occurs when an attacker forces a user to use a predetermined session ID, allowing the attacker to hijack the session after authentication. Django's session framework, while robust, can be vulnerable to fixation if not properly configured or if developers rely on insecure patterns.
The most common Django-specific manifestation involves manipulating the SESSION_COOKIE_NAME or using predictable session keys. Consider this vulnerable pattern:
# views.py
from django.shortcuts import render
def login(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
# Vulnerable: doesn't regenerate session ID
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('dashboard')
else:
return render(request, 'login.html', {'error': 'Invalid credentials'})
This code authenticates users but fails to regenerate the session ID upon login. An attacker could pre-generate a session ID, trick a victim into using it, then hijack the session after successful authentication.
Another Django-specific vulnerability arises from improper use of SESSION_ENGINE. If using file-based sessions without proper cleanup, session files might persist longer than intended, creating fixation opportunities:
# settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
SESSION_FILE_PATH = '/tmp/sessions/' # Insecure if not properly secured
Middleware ordering also matters in Django. If custom middleware manipulates sessions before Django's authentication middleware, it can create fixation vectors:
# middleware.py
class CustomSessionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Vulnerable: manipulates session before auth
if 'user_id' in request.session:
request.user = get_user(request.session['user_id'])
response = self.get_response(request)
return response
Django's session fixation can also occur through URL parameters when using SESSION_COOKIE_DOMAIN across subdomains without proper validation, allowing attackers to set cookies across related domains.
Django-Specific Detection
Detecting session fixation in Django requires both manual code review and automated scanning. middleBrick's Django-aware scanner specifically targets Django session management patterns to identify fixation vulnerabilities.
middleBrick scans for these Django-specific fixation indicators:
- Missing
session_regenerate_id()calls after authentication - Insecure session engine configurations
- Predictable session key generation patterns
- Middleware ordering issues that could enable fixation
- Cross-domain session cookie configurations
Here's how middleBrick identifies these issues in your Django application:
# Scan a Django API endpoint
middlebrick scan https://your-django-app.com/api/login
The scanner examines your Django application's session handling by:
- Analyzing the session backend configuration from Django's settings
- Checking for proper session ID regeneration after authentication
- Testing for predictable session ID patterns
- Verifying middleware ordering and session manipulation
For deeper Django-specific analysis, middleBrick can scan your OpenAPI spec alongside the runtime API:
# Scan with OpenAPI spec analysis
middlebrick scan --spec openapi.yaml https://your-django-app.com
This cross-references your Django authentication endpoints with actual runtime behavior, identifying discrepancies that could indicate fixation vulnerabilities.
Manual detection should include reviewing your Django authentication views for proper session handling:
# Vulnerable - missing session regeneration
def login_view(request):
if request.method == 'POST':
user = authenticate(request, username=request.POST['username'],
password=request.POST['password'])
if user is not None:
login(request, user)
return redirect('dashboard') # Vulnerable: session ID not regenerated
return render(request, 'login.html')
# Secure - proper session regeneration
def login_view_secure(request):
if request.method == 'POST':
user = authenticate(request, username=request.POST['username'],
password=request.POST['password'])
if user is not None:
login(request, user)
request.session.cycle_key() # Regenerates session ID
return redirect('dashboard')
return render(request, 'login.html')
Django-Specific Remediation
Remediating session fixation in Django requires implementing proper session management patterns and leveraging Django's built-in security features. The most critical fix is regenerating session IDs after authentication.
Here's the secure Django authentication pattern:
from django.contrib.auth import login, authenticate
from django.shortcuts import redirect, render
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
# Regenerate session ID to prevent fixation
request.session.cycle_key()
login(request, user)
return redirect('dashboard')
return render(request, 'login.html', {'error': 'Invalid credentials'})
For Django's session configuration, use these secure settings:
# settings.py
# Use secure session engine
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# Set secure cookie flags
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax' # or 'Strict' for maximum security
# Set proper session timeout
SESSION_COOKIE_AGE = 1209600 # 14 days in seconds
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# Use cryptographically secure session IDs
SESSION_RANDOM_KEY_LENGTH = 32
For Django applications using middleware, ensure proper ordering:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # Must come before auth
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Your custom middleware
'myapp.middleware.CustomSessionMiddleware',
]
For logout functionality, ensure complete session invalidation:
from django.contrib.auth import logout
def logout_view(request):
logout(request)
# Explicitly clear session data
request.session.flush()
return redirect('login')
When using Django's authentication views, you can override them with secure patterns:
from django.contrib.auth.views import LoginView
from django.utils.translation import gettext_lazy as _
class SecureLoginView(LoginView):
template_name = 'registration/login.html'
def form_valid(self, form):
# Call parent form_valid to authenticate
response = super().form_valid(form)
# Regenerate session ID after successful login
self.request.session.cycle_key()
return response
For Django REST Framework applications, implement session fixation protection in your authentication classes:
from rest_framework.authentication import SessionAuthentication
from rest_framework import exceptions
class SecureSessionAuthentication(SessionAuthentication):
def authenticate(self, request):
# Call parent authenticate
user, auth = super().authenticate(request)
# Regenerate session ID after successful authentication
if user and auth:
request.session.cycle_key()
return (user, auth)
Frequently Asked Questions
Does Django automatically protect against session fixation?
request.session.cycle_key() after successful login to prevent session fixation attacks. This is a common security oversight in Django applications.