Pii Leakage in Django with Basic Auth
Pii Leakage in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
Basic Authentication transmits credentials as base64-encoded strings in the Authorization header, which are easily decoded if intercepted. In Django, developers sometimes use Basic Auth for simplicity, particularly in internal services or APIs, but this choice interacts poorly with PII handling when protections are incomplete.
Django’s built-in authentication system supports HTTP Basic Auth via django.contrib.auth.authentication.BasicAuthentication (used with DRF or custom views). When combined with views that return PII—such as email addresses, usernames, phone numbers, or government IDs—in JSON responses, and without strict transport protections, leakage becomes likely. For example, a debug-enabled setting in production can cause full tracebacks containing PII to be returned in error responses. Similarly, if a view does not properly restrict access to authenticated data (e.g., returning another user’s profile), the API surface unintentionally exposes PII.
The risk is compounded when rate limiting, input validation, and authorization are weak or absent. Without per-request authorization checks (effectively missing BOLA/IDOR controls), an attacker who obtains a valid Basic Auth token (e.g., via phishing, credential reuse, or accidental logging) can enumerate usernames and exfiltrate PII at the endpoint. Because Basic Auth is static and rarely rotated compared to token-based flows, the window for exposure is larger. Logging mechanisms that capture headers may also persist base64 credentials or PII in plaintext logs, creating secondary leakage vectors. Even when TLS is used, misconfigured servers or deprecated protocols can weaken encryption, making interception feasible. In scans, this combination typically flags findings under Data Exposure, Authentication, and Input Validation categories, with remediation focused on tightening authorization, removing debug in production, and avoiding unnecessary PII returns.
Basic Auth-Specific Remediation in Django — concrete code fixes
Remediation centers on replacing or strictly constraining Basic Auth, enforcing authorization, and ensuring PII is not unnecessarily exposed. Below are concrete, working patterns for Django and Django REST Framework.
1. Use token-based auth instead of Basic Auth
Replace Basic Auth with token-based authentication. DRF provides built-in support that avoids transmitting credentials in every request and enables rotation.
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
class UserProfileView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
# Return only data relevant to the requesting user to prevent IDOR
profile = {
'username': request.user.username,
'email': request.user.email,
}
return Response(profile)
2. If Basic Auth is required, enforce HTTPS and strict authorization
If you must keep Basic Auth (for example, integrating with legacy systems), enforce HTTPS, use HTTP Strict Transport Security (HSTS), and add per-view authorization to prevent BOLA/IDOR. Never return other users’ PII.
from django.contrib.auth import authenticate
from django.http import JsonResponse
from django.views import View
from django.utils.decorators import method_decorator
from django.views.decorators.http import require_http_methods
from django.core.exceptions import PermissionDenied
import base64
@method_decorator(require_http_methods(["GET"]), name='dispatch')
class BasicAuthProfileView(View):
def dispatch(self, request, *args, **kwargs):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.lower().startswith('basic '):
return JsonResponse({'error': 'Unauthorized'}, status=401)
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 None:
return JsonResponse({'error': 'Unauthorized'}, status=401)
request.user = user
except Exception:
return JsonResponse({'error': 'Unauthorized'}, status=401)
return super().dispatch(request, *args, **kwargs)
def get(self, request):
# Ensure the requesting user can only see their own PII
target_username = request.GET.get('username')
if target_username and target_username != request.user.username:
raise PermissionDenied('You cannot view this resource.')
profile = {
'username': request.user.username,
'email': request.user.email,
}
return JsonResponse(profile)
3. Disable debug in production and sanitize responses
Ensure DEBUG = False and use middleware that removes sensitive data from error responses. Also, filter views to return minimal PII by default.
# settings.py
DEBUG = False
ALLOWED_HOSTS = ['api.example.com']
# Example middleware snippet to scrub sensitive headers/body in exceptions
import re
class SanitizeErrorMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
try:
response = self.get_response(request)
except Exception as e:
# Avoid exposing PII in tracebacks; log securely instead
response = JsonResponse({'error': 'Internal server error'}, status=500)
return response
4. Add rate limiting and logging controls
Use Django middleware or a reverse layer to limit brute-force attempts and ensure credentials and PII are not logged.
# Example simple rate limiting via middleware (conceptual)
from django.utils import timezone
from django.core.cache import cache
class RateLimitMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.startswith('/api/'):
key = f'ratelimit:{request.META.get("REMOTE_ADDR")}'
count = cache.get(key, 0)
if count >= 30: # 30 requests per window
return JsonResponse({'error': 'Rate limit exceeded'}, status=429)
cache.set(key, count + 1, timeout=60)
return self.get_response(request)
These steps reduce the likelihood of PII leakage by constraining how credentials are handled, enforcing least-privilege access, and ensuring responses do not overexpose data.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |