Prototype Pollution in Django with Basic Auth
Prototype Pollution in Django with Basic Auth
Prototype pollution in Django can occur when user-controlled input is merged into application objects or class prototypes, altering default behavior for all future instances. When Basic Authentication is used, an attacker must first bypass or satisfy the HTTP Basic Auth challenge, but if the application then reflects or reuses the provided credentials or associated metadata in object construction, the attack surface expands. For example, if a Django view deserializes JSON credentials from a Basic Auth–derived header and merges it into a dict or a class instance without validation, an attacker can inject properties such as __proto__, constructor, or other special keys. This can lead to unexpected behavior, including privilege escalation or data leakage, especially if the polluted object is later used in permission checks or serialization.
Consider a scenario where Basic Auth credentials are parsed and combined with user-supplied JSON for extended attributes. If the merging logic does not restrict key names, an attacker can send a crafted request with a header like Authorization: Basic d3JlY2E6cGFzc3dvcmQ= (user:web, pass:password) and a JSON body containing { "__proto__": { "isAdmin": true } }. In Python, depending on the merge implementation (e.g., using dict.update or a custom recursive merge), this can mutate the prototype of dictionaries or custom classes. The polluted prototype may affect later object creation, such as when Django serializers instantiate models or when permission utilities evaluate attributes. Although Django’s core ORM and model layer do not rely on JavaScript-style prototypes, custom Python classes or misuse of dictionaries can still be impacted, leading to logic flaws detectable by the Authentication and Property Authorization checks in a middleBrick scan.
During a black-box scan, middleBrick tests unauthenticated and authenticated surfaces where Basic Auth is enforced. It checks whether inputs derived from authentication headers or credentials are safely isolated from object creation paths. The scanner’s Authentication check identifies whether credentials are accepted over unencrypted channels, while Property Authorization assesses whether object properties are validated before use. Without proper input sanitization and strict schema validation, an API endpoint that accepts merged data from Basic Auth and user payloads can expose prototype pollution risks, which appear in the findings with severity and remediation guidance.
Basic Auth-Specific Remediation in Django
To mitigate prototype pollution in Django when using Basic Authentication, ensure that any user-influenced data is validated and isolated from internal objects. Avoid merging raw headers or credentials into dictionaries or model fields. Instead, explicitly extract only required values and apply strict allowlists. Below are concrete code examples demonstrating secure handling of Basic Auth credentials in Django views.
Secure Basic Auth Parsing and Validation
import base64
from django.http import JsonResponse
from django.views import View
from django.core.exceptions import PermissionDenied
import re
class SecureApiView(View):
def post(self, request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if not auth_header.startswith('Basic '):
return JsonResponse({'error': 'Unauthorized'}, status=401)
try:
encoded = auth_header.split(' ')[1]
decoded = base64.b64decode(encoded).decode('utf-8')
username, password = decoded.split(':', 1)
except (IndexError, UnicodeDecodeError, ValueError):
return JsonResponse({'error': 'Invalid credentials'}, status=401)
# Validate credentials against a secure source (e.g., Django user model)
if not self.is_valid_user(username, password):
return JsonResponse({'error': 'Invalid credentials'}, status=403)
# Explicitly parse user-supplied JSON separately
try:
data = json.loads(request.body)
except ValueError:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
# Apply strict allowlist for expected keys
allowed_keys = {'action', 'target', 'value'}
if not set(data.keys()).issubset(allowed_keys):
return JsonResponse({'error': 'Disallowed fields'}, status=400)
# Use data safely without merging with auth objects
result = self.process_request(username, data)
return JsonResponse(result)
def is_valid_user(self, username, password):
# Replace with secure credential verification
return re.match(r'^[\w.@+-]+$', username) is not None and len(password) >= 8
def process_request(self, username, data):
# Safe processing logic
return {'status': 'ok', 'user': username}
Using Django REST Framework with Explicit Parsing
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
import json
class DRFSecureEndpoint(APIView):
def post(self, request: Request) -> Response:
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.lower().startswith('basic '):
return Response({'detail': 'Unauthorized.'}, status=status.HTTP_401_UNAUTHORIZED)
# Decode credentials without merging into request data
import base64
payload = base64.b64decode(auth[6:]).decode('ascii')
username, _, password = payload.partition(':')
# Validate credentials via Django authentication backend
from django.contrib.auth import authenticate
user = authenticate(request, username=username, password=password)
if user is None:
return Response({'detail': 'Invalid credentials.'}, status=status.HTTP_403_FORBIDDEN)
# Parse and validate incoming JSON separately
try:
body = json.loads(request.body)
except ValueError:
return Response({'detail': 'Malformed JSON.'}, status=status.HTTP_400_BAD_REQUEST)
# Allowlist approach
allowed = {'query', 'filters', 'limit'}
if any(k not in allowed for k in body.keys()):
return Response({'detail': 'Invalid field.'}, status=status.HTTP_400_BAD_REQUEST)
# Process safely
return Response({'user': username, 'received': list(body.keys())})
These examples demonstrate strict separation between authentication data and user input, avoiding any merging that could lead to prototype pollution. Always validate and sanitize inputs, and prefer explicit schemas over dynamic property assignment.