Replay Attack in Django with Basic Auth
Replay Attack in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
A replay attack occurs when an attacker intercepts a valid authentication request and retransmits it to gain unauthorized access. In Django, using HTTP Basic Auth over non-encrypted channels amplifies this risk because the credentials are base64-encoded, not encrypted. Without TLS, an attacker on the network can capture the Authorization header and replay it to access protected resources. Even with TLS, if the request includes an API token or session-like credential transmitted via Basic Auth headers, and the server does not enforce strict nonce or timestamp validation, intercepted messages can be reused.
Django’s built-in Basic Auth support (e.g., via django.contrib.auth.authentication.BasicAuthentication in custom views or third‑party packages) typically validates the username and password on each request but does not inherently prevent replay unless additional protections are added. For example, if a view decodes the Basic Auth credentials, authenticates the user, and grants access without ensuring request uniqueness, an attacker can capture a valid Authorization header like Authorization: Basic dXNlcjpwYXNz and resend it to the same endpoint. Because Basic Auth does not include a built‑in challenge‑response mechanism, the server treats the replayed header as legitimate.
Real‑world attack patterns mirror the OWASP API Top 10 A07:2021 — Identification and Authentication Failures. Consider an API endpoint that relies solely on Basic Auth over HTTPS but lacks per‑request nonces or timestamps. An attacker performing SSRF or network eavesdropping can log and replay the header. In Django, if the view does not bind the authentication to a per‑request token or timestamp, the replay succeeds. This is especially dangerous in microservice or CI/CD environments where staging APIs might be exposed without continuous monitoring, increasing the chance of intercepted credentials being reused.
To detect such risks, scanners like middleBrick run unauthenticated checks that analyze whether the server includes anti‑replay mechanisms. They inspect whether the API relies on static credentials transmitted in each request and whether there are safeguards like one‑time tokens or strict timestamp windows. Without these, the API remains vulnerable to replay despite using Basic Auth, as the authentication context is not tied to a unique transaction.
Basic Auth-Specific Remediation in Django — concrete code fixes
Remediation focuses on ensuring that each request is unique and bound to a secure context. The primary approach is to avoid relying solely on Basic Auth for state‑free replay‑prone endpoints. Instead, use HTTPS always, and augment Basic Auth with nonce/timestamp validation or migrate to token‑based schemes where feasible. Below are concrete Django examples demonstrating secure practices.
1. Enforce HTTPS and Reject Non‑Secure Requests
Ensure your Django project redirects all HTTP traffic to HTTPS and validates that requests using Basic Auth arrive over secure channels.
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
2. Add Per‑Request Nonce or Timestamp Validation
Create a middleware that checks a timestamp or nonce present in a custom header, ensuring recent requests are not replayed. This works alongside Basic Auth by adding an extra layer of transaction uniqueness.
# middleware.py
import time
from django.http import JsonResponse
class ReplayProtectionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.seen_nonces = set()
def __call__(self, request):
timestamp = request.META.get('HTTP_X_REQUEST_TIMESTAMP')
nonce = request.META.get('HTTP_X_REQUEST_NONCE')
if not timestamp or not nonce:
return JsonResponse({'error': 'Missing timestamp or nonce'}, status=400)
# Reject old requests (>2 minutes)
if abs(time.time() - int(timestamp)) > 120:
return JsonResponse({'error': 'Request expired'}, status=400)
if nonce in self.seen_nonces:
return JsonResponse({'error': 'Replay detected'}, status=403)
self.seen_nonces.add(nonce)
response = self.get_response(request)
return response
Then include this middleware in MIDDLEWARE and require clients to send X-Request-Timestamp and X-Request-Nonce headers alongside the Basic Auth header.
3. Example View Combining Basic Auth with Nonce Check
A view that decodes Basic Auth credentials only after confirming the request’s uniqueness.
# views.py
import base64
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.contrib.auth import authenticate
@require_http_methods(["GET"])
def protected_view(request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.startswith('Basic '):
return JsonResponse({'error': 'Unauthorized'}, status=401)
# Verify nonce/timestamp via middleware before proceeding
try:
encoded = auth.split(' ')[1]
decoded = base64.b64decode(encoded).decode('utf-8')
username, password = decoded.split(':', 1)
user = authenticate(request, username=username, password=password)
if user is not None:
return JsonResponse({'message': f'Authenticated as {user.username}'})
return JsonResponse({'error': 'Invalid credentials'}, status=401)
except Exception:
return JsonResponse({'error': 'Bad request'}, status=400)
4. Using Token-Based Alternatives Where Possible
For new endpoints, consider replacing Basic Auth with token authentication (e.g., JWT) to avoid replay concerns tied to static credentials. If you must keep Basic Auth, rotate credentials frequently and monitor via continuous scanning tools like middleBrick Pro, which can flag missing replay protections in CI/CD pipelines.