Information Disclosure in Django with Basic Auth
Information Disclosure in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
HTTP Basic Authentication sends credentials as a colon-separated username:password string encoded with Base64, not encrypted. In Django, if an endpoint is configured to use Basic Auth and also discloses information in error messages, debug pages, or verbose logs, the combination can lead to Information Disclosure. Even when authentication succeeds, responses may reveal details such as usernames, endpoint behavior, or stack traces that expose internal logic.
Django’s django.contrib.auth.decorators.login_required and permission classes do not implement Basic Auth by default; developers commonly add it via request headers. If the view or surrounding middleware writes the decoded username to logs or includes it in JSON error responses, an attacker who can observe or intercept responses (e.g., via a proxy or misconfigured CORS) can harvest usernames. Insecure default configurations or permissive ALLOWED_HOSTS can further widen the exposure surface.
Consider an API endpoint that returns a detailed error when malformed input is provided. If the error page includes the authenticated username parsed from the Authorization header, an unauthenticated attacker can enumerate valid usernames by inducing errors. Similarly, verbose Django debug pages in development mode may echo environment variables or settings that reference account names, aiding reconnaissance for further attacks such as BOLA/IDOR or credential stuffing.
Middleware or decorators that integrate Basic Auth must avoid attaching user identity to logs or responses unless necessary and properly controlled. For example, a naive implementation might decode the header and store the username in a log message: logger.info(f'Authenticated as {username}'). If logs are centralized or exposed through an insecure endpoint, this becomes a disclosure channel. Cross-referencing OpenAPI specs with runtime findings can highlight mismatches where documentation claims authentication is required, but responses leak identity information in error payloads.
Real-world patterns include endpoints that return HTTP 401 with a WWW-Authenticate header but also include custom JSON bodies that inadvertently expose user context. In CI/CD pipelines, scanning with the middleBrick CLI can detect such disclosure patterns by correlating spec definitions with runtime behavior, ensuring that authenticated routes do not leak identity in failure paths.
Basic Auth-Specific Remediation in Django — concrete code fixes
To mitigate Information Disclosure when using Basic Auth in Django, ensure credentials are never reflected in responses, logs, or error payloads. Use Django’s built-in decorators and middleware carefully, and sanitize any logging that includes user identity. Below are concrete, secure patterns.
1. Secure Basic Auth parsing without leaking identity
Decode the Authorization header only when necessary, and avoid logging the decoded username. Use constant-time comparison where feasible and ensure errors are generic.
import base64
from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_http_methods
def get_basic_auth_credentials(request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.lower().startswith('basic '):
return None, None
try:
encoded = auth.split(' ', 1)[1].strip()
decoded = base64.b64decode(encoded).decode('utf-8')
username, password = decoded.split(':', 1)
return username, password
except Exception:
return None, None
@require_http_methods(["GET"])
def secure_view(request):
username, password = get_basic_auth_credentials(request)
if not username or not password:
return HttpResponse('Unauthorized', status=401, headers={'WWW-Authenticate': 'Basic'})
# Validate credentials using Django’s auth backend
from django.contrib.auth import authenticate
user = authenticate(request, username=username, password=password)
if user is None:
return HttpResponse('Forbidden', status=403)
# Do NOT log username or include it in responses
return JsonResponse({'status': 'ok'})
2. Disable debug and verbose errors in production
Ensure DEBUG = False and configure ALLOWED_HOSTS strictly. Use middleware to sanitize error responses so that stack traces and usernames are not exposed.
# settings.py
DEBUG = False
ALLOWED_HOSTS = ['api.example.com']
# Custom handler to avoid leaking information
handler401 = 'myapp.views.custom_401'
handler403 = 'myapp.views.custom_403'
handler404 = 'myapp.views.custom_404'
handler500 = 'myapp.views.custom_500'
3. Secure logging practices
Filter or omit usernames from log messages. Use structured logging with redaction for sensitive fields.
import logging
logger = logging.getLogger(__name__)
def safe_log_auth(request):
# Avoid logging credentials or usernames
logger.info('Authentication attempt', extra={'status': 'attempted'})
4. Use HTTPS and secure headers
Always serve Basic Auth over HTTPS to prevent credential interception. Add security headers to reduce information leakage.
# settings.py or middleware
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
5. MiddleBrick integration for continuous checks
Use the middleBrick CLI to scan endpoints and verify that authenticated routes do not disclose identity in error responses. The GitHub Action can enforce a minimum score threshold in CI/CD, while the MCP Server allows scanning directly from AI coding assistants during development.
# Example CLI usage
middlebrick scan https://api.example.com/secure-endpoint