Memory Leak in Django with Basic Auth
Memory Leak in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
A memory leak in a Django service that uses HTTP Basic Authentication can arise when request handling code does not properly release objects created per request. With Basic Auth, the browser sends an Authorization header on every request. Django decodes this header on each request, and if views or middleware allocate data that grows over time—such as accumulating parsed credentials, attaching large objects to the request, or caching user-specific data without cleanup—the process memory footprint can increase steadily under sustained load.
When combined with unauthenticated scanning, middleBrick tests endpoints that accept Basic Auth headers and observes behavior across repeated calls. Repeated calls with slightly varied credentials can expose patterns where per-request allocations are not freed, especially when developers store parsed user information on the request object for convenience. For example, attaching a user object or a large decoded payload to request and failing to rely on short-lived local variables can keep references alive longer than intended. Over time, this leads to increased resident memory, slower garbage collection, and in extreme cases, process restarts or degraded performance.
The risk is compounded when Basic Auth is used without TLS, because middleware may log or inspect headers, and when endpoints return large responses that are cached or accumulated in per-request buffers. middleBrick’s unauthenticated scan can surface these issues by running repeated, parallel checks against the same endpoint, highlighting irregularities in memory-related findings within the Data Exposure and Unsafe Consumption checks.
Basic Auth-Specific Remediation in Django — concrete code fixes
To reduce memory retention when using Basic Auth in Django, ensure that per-request data is kept short-lived and avoid attaching large or long-lived objects to the request. Prefer extracting credentials only when needed and releasing references after use. Below are concrete patterns to follow.
Secure Basic Auth view pattern
Decode credentials inside the view, use them immediately, and avoid storing them or derived objects on the request.
import base64
from django.http import HttpResponse, HttpResponseForbidden
from django.views import View
class SecureBasicAuthView(View):
def dispatch(self, request, *args, **kwargs):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.startswith('Basic '):
return HttpResponseForbidden('Unauthorized')
token = auth.split(' ', 1)[1]
decoded = base64.b64decode(token).decode('utf-8')
username, _, password = decoded.partition(':')
# Validate credentials (e.g., against Django user model or a service)
if not self.is_valid(username, password):
return HttpResponseForbidden('Invalid credentials')
# Do NOT set request.user = custom_object or attach large data
return super().dispatch(request, *args, **kwargs)
def is_valid(self, username, password):
# Replace with secure validation, e.g., constant-time compare
return username == 'admin' and password == 's3cr3t'
def get(self, request):
return HttpResponse('Authenticated OK')
Middleware that avoids request pollution
If you use middleware, keep it lean. Do not attach user instances or large caches to the request; use local variables and return early on failure.
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponseForbidden
import base64
class LightweightBasicAuthMiddleware(MiddlewareMixin):
def process_request(self, request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.startswith('Basic '):
return None # No credentials; let the view handle it
try:
token = auth.split(' ', 1)[1]
decoded = base64.b64decode(token).decode('utf-8')
username, _, password = decoded.partition(':')
if not self.is_valid(username, password):
return HttpResponseForbidden('Unauthorized')
# Do NOT assign to request.user or request.credentials
except Exception:
return HttpResponseForbidden('Bad credentials format')
return None
def is_valid(self, username, password):
# Validate securely; avoid heavy per-request allocations
return username == 'svc' and password == 'token'
Operational recommendations
- Always use HTTPS with Basic Auth to prevent credentials from leaking in headers that may be logged.
- Avoid caching responses that contain Authorization headers or user-specific data unless cache keys are carefully scoped.
- Monitor memory usage in production; a steady increase in RSS per worker can indicate a leak.
- Consider migrating to token-based authentication (e.g., JWT in Authorization header) to reduce the frequency of credential parsing and the risk of accidental retention.