Skip to main content

Overview

Path Traversal (also known as Directory Traversal) allows an attacker to read arbitrary files on the server. The vulnerability occurs when an application uses user-supplied input to construct a path to a file or directory without proper validation. By using ../ sequences, an attacker can navigate outside of the intended directory to access sensitive files anywhere on the server’s file system.

Business Impact

This vulnerability can lead to the complete disclosure of application source code, configuration files containing credentials, business data, and sensitive operating system files. This information leak is often a precursor to a full system compromise.

Reference Details

CWE ID: CWE-22 OWASP Top 10 (2021): A01:2021 - Broken Access Control Severity: High

Framework-Specific Analysis and Remediation

No framework can automatically protect against this logical vulnerability. The developer is always responsible for sanitizing and validating user input before using it in any file system operation. The core principle is to ensure the final, resolved (canonical) path of the requested file is located within the expected, secure base directory.
  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Django does not provide a specific file-serving view for arbitrary files, forcing developers to write their own. This is where vulnerabilities are often introduced. The key is to use Python’s os.path module to safely construct and validate paths.

Vulnerable Scenario 1: A Document Download View

An endpoint allows users to download invoices by providing a filename.
# invoicing/views.py
import os
from django.http import HttpResponse

def download_invoice(request, filename):
    # DANGEROUS: The filename is directly concatenated to the base path.
    # An attacker can request a URL like /invoices/download/../../../../etc/passwd
    file_path = os.path.join('/var/www/invoices/', filename)
    if os.path.exists(file_path):
        with open(file_path, 'rb') as fh:
            return HttpResponse(fh.read(), content_type="application/pdf")
    # ...

Vulnerable Scenario 2: Dynamic Template Loading

A feature loads a custom user theme template based on a cookie value.
# themes/utils.py
def load_user_theme(request):
    theme_name = request.COOKIES.get('theme', 'default')
    # DANGEROUS: The theme name is used to construct a path to an include file.
    template_path = f"themes/{theme_name}.html"
    return render_to_string(template_path)

Mitigation and Best Practices

Never trust the filename. Use os.path.basename to strip any directory information. Then, construct the full path and use os.path.abspath to resolve it to its canonical form. Finally, check that this resolved path starts with the secure base directory’s path.

Secure Code Example

# invoicing/views.py (Secure Version)
import os
from django.http import HttpResponse, Http404

def download_invoice(request, filename):
    # Define the secure base directory where invoices are stored.
    base_dir = os.path.abspath('/var/www/invoices/')
    
    # 1. Strip any directory traversal characters from the filename.
    safe_filename = os.path.basename(filename)
    
    # 2. Construct the full path safely.
    file_path = os.path.join(base_dir, safe_filename)
    
    # 3. Resolve the path to its absolute form and check if it's within the base directory.
    if not os.path.abspath(file_path).startswith(base_dir):
        raise Http404("Invalid path detected.")
        
    if os.path.exists(file_path):
        with open(file_path, 'rb') as fh:
            return HttpResponse(fh.read(), content_type="application/pdf")
    else:
        raise Http404("File not found.")

Testing Strategy

Write integration tests that request files using path traversal payloads (../, ..%2f, etc.). The tests should assert that the application returns a 404 Not Found or 403 Forbidden response, not the content of the targeted sensitive file.
# invoicing/tests.py
def test_path_traversal_attack_is_blocked(self):
    # Attempt to access a file outside the intended directory
    traversal_payload = "../../../../../etc/passwd"
    url = reverse('download-invoice', args=[traversal_payload])
    
    response = self.client.get(url)
    
    # A secure implementation should detect the invalid path and return an error.
    self.assertEqual(response.status_code, 404)