External control of file name or path - Python

External control of file name or path - Python

Need

Prevention of unauthorized access and manipulation of file paths and names

Context

  • Usage of Python 3.0+ for developing Python applications
  • Usage of Django for building web applications in Python

Description

Non compliant code

        from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import os

@csrf_exempt
def upload_file(request):
    if request.method == 'POST':
        file = request.FILES['file']
        filename = request.POST['filename']
        with open(filename, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)
        return HttpResponse("File uploaded successfully")
    else:
        return HttpResponse("Failed to upload file")
        
        

This piece of code is an endpoint for a Django application that accepts file uploads. It is vulnerable to Path Traversal attacks because it uses user-provided data to determine the path where the uploaded file will be saved.

The vulnerability lies in the line:

                filename = request.POST['filename']

            


Here, the filename is taken directly from the user input without any sanitization or validation. This filename is then used to open a file:

                with open(filename, 'wb+') as destination:

            


An attacker could exploit this vulnerability by providing a path instead of a simple filename. For example, they could provide a filename like ../../../etc/passwd, which could overwrite critical system files if the application has the necessary permissions. This could lead to information disclosure, denial of service, or even remote code execution if the overwritten file is executable and run by the system.

To mitigate this vulnerability, the application should never use user-provided data to construct file paths. Instead, it should generate its own filenames, or at least sanitize and validate the user-provided filenames to ensure they do not contain any path traversal sequences or other dangerous characters.

Steps

  • Validate the filename input to ensure it only contains allowed characters. This can prevent directory traversal attacks.
  • Do not allow the user to specify the entire path of the file. Instead, use a predetermined directory to store uploaded files and only allow the user to specify a filename.
  • Consider using a library or built-in function to safely join the user-specified filename with the predetermined directory.
  • Ensure that the application has the least privilege on the file system. It should only have write access to the specific directory where files are stored.
  • Implement a file type verification mechanism to ensure that only files of the expected type are uploaded.

Compliant code

        from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.core.exceptions import ValidationError
import os
import re

UPLOAD_DIR = '/path/to/upload/directory'  # Predetermined upload directory

@csrf_exempt
def upload_file(request):
    if request.method == 'POST':
        file = request.FILES['file']
        filename = request.POST['filename']

        # Validate filename
        if not re.match(r'^[\\w,\\s-]+\\.[A-Za-z]{3}$', filename):
            raise ValidationError("Invalid filename")

        # Safely join the filename with the upload directory
        safe_filename = os.path.join(UPLOAD_DIR, filename)

        with open(safe_filename, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)
        return HttpResponse("File uploaded successfully")
    else:
        return HttpResponse("Failed to upload file")
        
        

The updated code includes several changes to address the vulnerability:

1. Predetermined Upload Directory: The UPLOAD_DIR variable is set to a predetermined directory where all uploaded files will be stored. This prevents the user from specifying the entire path of the file, which could potentially allow them to overwrite important system files.

2. Filename Validation: The re.match() function is used to validate the filename provided by the user. This ensures that the filename only contains allowed characters (alphanumeric characters, underscores, spaces, hyphens, and a three-letter file extension), which can prevent directory traversal attacks.

3. Safe File Path Construction: The os.path.join() function is used to safely join the user-specified filename with the predetermined upload directory. This ensures that the file will be saved in the correct location, even if the filename provided by the user includes special characters or relative path components.

4. Least Privilege: Although not shown in the code, it is recommended to ensure that the application has the least privilege on the file system. It should only have write access to the specific directory where files are stored. This can prevent an attacker from exploiting this vulnerability to gain unauthorized access to other parts of the file system.

5. File Type Verification: Although not shown in the code, it is recommended to implement a file type verification mechanism to ensure that only files of the expected type are uploaded. This can prevent an attacker from uploading malicious files that could be executed by the server or other users.

References