HIGH path traversaldjangobasic auth

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 IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Does Basic Authentication prevent Path Traversal in Django?
No. Basic Authentication verifies credentials but does not validate user input. Path Traversal depends on how file paths are constructed and resolved, so unchecked parameters can still escape intended directories even when requests include valid Basic Auth headers.
What is the most reliable way to secure file serving in Django?
Avoid dynamic filesystem paths based on user input. Use Django's 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.