Skip to main content

Inappropriate coding practices - invalid file

Need

Enforce proper file validation and verification in the repository

Context

  • Usage of Python 3 for scripting and development
  • Usage of Django for building web applications

Description

Non compliant code

from django.core.files import File

def save_file(request):
if request.method == 'POST':
file = request.FILES['myfile']
destination = open('some_directory/' + file.name, 'wb+')
for chunk in file.chunks():
destination.write(chunk)
destination.close()

In the code snippet above, we are dealing with file uploads in a Django backend application. The function save_file(request) is intended to handle POST requests that contain a file. The file is retrieved from the request, opened, and written to a specified directory.

The vulnerability here is that there is no validation or verification of the uploaded file's content or its extension. An attacker could upload a malicious file with an incorrect or misleading extension, which could then be executed or used inappropriately by other parts of the application or by unsuspecting users.

For instance, an attacker could upload a file with a .jpg extension that is actually a Python script. If this file is then used in the context where a .jpg file is expected, it could lead to unexpected and potentially harmful behavior.

The code does not ensure that the file's content matches its extension, leading to a potential security risk.

Steps

  • Validate the file extension against a whitelist of allowed extensions.
  • Check the file's MIME type to ensure it matches the file extension.
  • Use a library or built-in function to sanitize the filename to prevent directory traversal attacks.
  • Implement error handling to manage cases where the file cannot be saved or read.
  • Consider storing the files in a separate, secure location outside of the application's directory.

Compliant code

import os
import mimetypes
from django.core.files import File
from django.core.files.storage import FileSystemStorage

def save_file(request):
if request.method == 'POST':
file = request.FILES['myfile']
file_extension = os.path.splitext(file.name)[1] # Get file extension
allowed_extensions = ['.jpg', '.png', '.pdf'] # Define allowed extensions

# Validate file extension
if file_extension not in allowed_extensions:
return "Invalid file extension"

# Check MIME type
mime_type = mimetypes.guess_type(file.name)[0]
if mime_type is None or not mime_type.startswith('image/'):
return "Invalid MIME type"

# Sanitize filename
safe_filename = os.path.basename(file.name)

# Use Django's built-in FileSystemStorage to handle file storage
fs = FileSystemStorage(location='some_directory')
filename = fs.save(safe_filename, file)
file_url = fs.url(filename)

return file_url

The updated code includes several security improvements:

  1. File Extension Validation: The code now checks the file extension against a list of allowed extensions. This helps ensure that only files of the correct type are saved.

  2. MIME Type Checking: The code checks the MIME type of the file to ensure it matches the file extension. This is an additional layer of security to prevent malicious files from being uploaded.

  3. Filename Sanitization: The code uses os.path.basename to sanitize the filename. This prevents directory traversal attacks where an attacker could potentially access sensitive files by manipulating the filename.

  4. File Storage: The code uses Django's built-in FileSystemStorage to handle file storage. This abstracts away the details of file storage and provides additional security features.

  5. Error Handling: While not explicitly shown in the code, it's important to implement error handling to manage cases where the file cannot be saved or read. This could be done with try/except blocks around the file saving code.

  6. Secure File Location: The files are stored in a separate, secure location outside of the application's directory. This helps protect the files from being accessed if the application is compromised.

References