Path Traversal in Django with Basic Auth
Path Traversal in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
Path Traversal occurs when an application permits user-supplied input to reach the filesystem without sufficient validation, enabling sequences like ../../../ to escape intended directories. In Django, this commonly surfaces through file-serving views, user-controlled document identifiers, or dynamic path concatenation. When Basic Authentication is used, the presence of credentials does not inherently mitigate traversal risks; it only changes the trust boundary. If an endpoint accepts a filename or path parameter after Basic Auth validation, an authenticated context may encourage developers to assume safety, but the underlying path manipulation remains unchecked.
Consider a Django view that serves user-uploaded documents using a path derived from a query parameter after Basic Auth succeeds:
import os
from django.http import HttpResponse, HttpResponseForbidden
from django.contrib.auth.decorators import login_required
def serve_document(request):
# WARNING: vulnerable to path traversal
filename = request.GET.get('file', '')
base = '/var/app/documents/'
path = os.path.join(base, filename)
if not os.path.exists(path):
return HttpResponseForbidden('Not found')
with open(path, 'rb') as f:
content = f.read()
return HttpResponse(content, content_type='application/octet-stream')
Even with Basic Auth enforced via login_required or middleware, the filename input is taken directly from the client. An attacker who authenticates successfully can supply ../../../etc/passwd, causing the application to read arbitrary files. This is a classic Path Traversal vector combined with Basic Auth: the authentication check passes, but the file resolution logic fails to canonicalize and restrict the path. The risk is not theoretical; real-world incidents have involved similar patterns where authenticated file-serving endpoints exposed sensitive system files.
Django’s own URL routing and view logic do not automatically protect against traversal if you manually concatenate paths. The framework does not treat user input as safe simply because the request includes valid Basic Auth credentials. Moreover, misconfigured STATIC_ROOT or MEDIA_ROOT combined with insufficient validation can amplify the impact. The combination of Basic Auth and Path Traversal is particularly insidious because developers may conflate access control with input validation, leading to overlooked sanitization where it matters most.
Basic Auth-Specific Remediation in Django — concrete code fixes
To mitigate Path Traversal in Django when using Basic Authentication, focus on strict input validation, canonical path resolution, and avoiding direct filesystem access based on user input. Below are concrete, safe patterns.
1. Use os.path.realpath and an allowlist
Resolve the absolute path and ensure it remains within the intended directory. Do not rely on os.path.join alone, as it does not prevent directory traversal.
import os
from django.http import HttpResponse, HttpResponseForbidden
def serve_document_safe(request):
filename = request.GET.get('file', '')
base = '/var/app/documents/'
# Resolve symlinks and normalize
safe_base = os.path.realpath(base)
# Build candidate and resolve again
candidate = os.path.realpath(os.path.join(safe_base, filename))
# Ensure the candidate is still under the base
if not candidate.startswith(safe_base + os.sep) and candidate != safe_base:
return HttpResponseForbidden('Invalid path')
if not os.path.exists(candidate) or not os.path.isfile(candidate):
return HttpResponseForbidden('Not found')
with open(candidate, 'rb') as f:
content = f.read()
return HttpResponse(content, content_type='application/octet-stream')
2. Use Django’s FileResponse with validated paths
Django provides streaming file responses that work safely when the filesystem path is controlled.
from django.http import FileResponse, HttpResponseForbidden
import os
def serve_with_fileresponse(request):
filename = request.GET.get('file', '')
base = '/var/app/documents/'
# Restrict to alphanumeric and limited safe characters
if not filename.replace('.', '').replace('_', '').replace('-', '').isalnum():
return HttpResponseForbidden('Invalid filename')
path = os.path.join(base, filename)
if not os.path.exists(path) or not os.path.isfile(path):
return HttpResponseForbidden('Not found')
response = FileResponse(open(path, 'rb'))
return response
3. Enforce Basic Auth at the web server or middleware level
Instead of implementing Basic Auth in Django views, consider handling it at the reverse proxy or load balancer. This reduces the attack surface within application code. If you must use Django for authentication, combine it with proper decorators and input checks.
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
def basic_auth_login_required(view_func):
# Custom decorator that checks HTTP Basic Auth before proceeding
def wrapped(request, *args, **kwargs):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if not auth_header.startswith('Basic '):
response = HttpResponse(status=401)
response['WWW-Authenticate'] = 'Basic realm="Access"'
return response
# Decode and validate credentials (simplified)
import base64
credentials = base64.b64decode(auth_header.split(' ')[1]).decode('utf-8')
username, password = credentials.split(':', 1)
if username != 'admin' or password != 'secret':
response = HttpResponse(status=403)
return response
request.user = type('User', (), {'username': username})()
return view_func(request, *args, **kwargs)
return wrapped
@basic_auth_login_required
def protected_view(request):
return HttpResponse('OK')
These examples illustrate how to combine authentication with secure path handling. The key takeaway is that Basic Auth confirms identity but does not validate or sanitize inputs; explicit checks are required to prevent Path Traversal.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |
Frequently Asked Questions
Does Basic Authentication prevent Path Traversal in Django?
What is the most reliable way to secure file serving in Django?
FileResponse with strictly validated filenames, resolve paths with os.path.realpath, enforce an allowlist of permitted characters, and ensure the resolved path remains within a dedicated base directory. Consider offloading file delivery to a web server with strict access controls.