Skip to main content

Overview

This vulnerability occurs when an application allows users to upload files without sufficiently restricting the file type, file content, or storage location. Attackers can upload malicious files disguised as benign ones (e.g., a PHP script named avatar.jpg.php or shell.php saved as image.gif). If the server later executes or serves this file incorrectly, it can lead to Remote Code Execution (RCE), Cross-Site Scripting (XSS), or Denial of Service (DoS). 📁💣

Business Impact

Unrestricted file uploads are extremely dangerous:
  • Remote Code Execution: Uploading and executing a web shell (e.g., .php, .jsp, .aspx) grants the attacker full control over the server.
  • Cross-Site Scripting (XSS): Uploading HTML or SVG files containing JavaScript can lead to stored XSS attacks against other users who view the file.
  • Denial of Service: Uploading extremely large files or files designed to crash parsers (e.g., “zip bombs”) can exhaust server resources.
  • Phishing/Malware Distribution: The site can be used to host malicious files targeting users.

Reference Details

CWE ID: CWE-434 OWASP Top 10 (2021): A04:2021 - Insecure Design Severity: Critical (if RCE is possible)

Framework-Specific Analysis and Remediation

This is a common design flaw where developers don’t fully consider the implications of file uploads. Robust defense requires multiple layers:
  1. Strict Allow-list Validation: Only permit specific, safe file extensions (e.g., .jpg, .png, .pdf). Never rely on a block-list.
  2. Content Type Verification: Check the Content-Type header, but do not trust it solely. Validate it against the extension.
  3. File Content Inspection: Use libraries (like python-magic, Apache Tika) to verify the file’s actual content matches the claimed type (e.g., ensure a .jpg file actually contains JPEG data).
  4. Rename Files: Store uploaded files with a randomly generated filename and without the original extension. Store the original filename and content type separately in a database if needed.
  5. Secure Storage Location: Store uploaded files outside the web root or in a directory where server-side script execution is disabled (e.g., via .htaccess or Nginx config).
  6. Serve Files Securely: Serve files with the correct Content-Type and add Content-Disposition: attachment to force download for potentially risky types. Consider using a separate domain or CDN for user uploads.

  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Handling request.FILES in Django or request.files in Flask without proper validation.

Vulnerable Scenario 1: Basic Upload without Validation

# views/upload.py
from django.core.files.storage import default_storage

def upload_profile_picture(request):
    if request.method == 'POST' and request.FILES.get('profile_pic'):
        file = request.FILES['profile_pic']
        # DANGEROUS: No validation of filename, extension, or content type.
        # An attacker could upload "shell.php" disguised as "image.jpg".
        # If saved within webroot with original name, it could be executed.
        filename = default_storage.save(file.name, file)
        # ... update user profile with filename ...
        return HttpResponse("Upload successful")
    return render(request, 'upload_form.html')

Vulnerable Scenario 2: Relying Only on Content-Type Header

# views/upload_content_type.py
def upload_document(request):
    if request.method == 'POST' and request.FILES.get('document'):
        file = request.FILES['document']
        # DANGEROUS: Content-Type header can be easily spoofed by attacker.
        # Attacker uploads shell.php but sets Content-Type to application/pdf.
        if file.content_type == 'application/pdf':
            filename = default_storage.save(file.name, file)
            return HttpResponse("PDF Uploaded")
        else:
            return HttpResponse("Invalid file type", status=400)
    # ...

Mitigation and Best Practices

Validate the extension against an allow-list. Use a library like python-magic to check the actual file content. Generate a random filename. Store the file outside the web root or in a non-executable location.

Secure Code Example

# views/upload_secure.py
import os
import uuid
import magic # Requires python-magic library
from django.conf import settings
from django.core.files.storage import FileSystemStorage

# SECURE: Define allowed extensions and MIME types
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif'}
ALLOWED_MIME_TYPES = {'image/jpeg', 'image/png', 'image/gif'}

# SECURE: Configure storage outside web root
upload_storage = FileSystemStorage(location=settings.MEDIA_ROOT_SECURE) # Define in settings.py

def upload_secure(request):
    if request.method == 'POST' and request.FILES.get('picture'):
        file = request.FILES['picture']

        # 1. Check extension
        ext = os.path.splitext(file.name)[1].lower()
        if ext not in ALLOWED_EXTENSIONS:
            return HttpResponse("Invalid file extension", status=400)

        # 2. Check Content-Type header (less reliable, but a layer)
        if file.content_type not in ALLOWED_MIME_TYPES:
             return HttpResponse("Invalid reported MIME type", status=400)

        # 3. Check file content using python-magic
        # Read a chunk to avoid loading huge files into memory
        file_content_chunk = file.read(2048) # Read first 2KB
        file.seek(0) # Reset file pointer
        mime_type = magic.from_buffer(file_content_chunk, mime=True)
        if mime_type not in ALLOWED_MIME_TYPES:
            return HttpResponse("Invalid file content detected", status=400)

        # 4. Generate random filename
        random_filename = f"{uuid.uuid4()}{ext}"

        # 5. Save to secure location
        filename = upload_storage.save(random_filename, file)

        # Store mapping of random_filename to original_filename in DB if needed
        # ...
        return HttpResponse(f"Secure upload successful: {filename}")
    # ...

Testing Strategy

Attempt to upload files with disallowed extensions (.php, .html, .exe). Attempt to upload files with double extensions (.jpg.php). Attempt to upload a valid PHP script renamed to .jpg. Attempt to upload a file with a correct extension but incorrect content (e.g., text file named .png). Verify that all attempts except legitimate, allowed files are rejected. Check the storage location and filenames on the server.