Skip to content

CWE-434: Unrestricted File Upload

Overview

Unrestricted file upload vulnerabilities occur when applications accept file uploads without properly validating file type, content, size, or destination. Attackers exploit this to upload malicious files including web shells (PHP, JSP, ASPX), executable malware, HTML files with XSS payloads, SVG files with embedded JavaScript, or oversized files causing denial of service. The vulnerability is particularly dangerous when uploaded files are stored within the webroot and can be directly accessed and executed by the web server.

OWASP Classification

A06:2025 - Insecure Design

Risk

Critical: Can lead to remote code execution, complete server compromise, malware distribution, stored XSS, denial of service, and defacement. Unrestricted uploads combined with path traversal can overwrite critical system files. Even "safe" file types like images can contain malicious metadata or be used for social engineering attacks.

Remediation Steps

Core principle: Validate file type through content inspection (not just extension), store uploads outside webroot, rename files, and never execute or serve uploaded files in their original format without sanitization.

Validate File Type by Content

# VULNERABLE - trusts file extension
if filename.endswith('.jpg'):
    save_file(filename, data)

# SECURE - validate actual file content
import magic

ALLOWED_TYPES = {'image/jpeg', 'image/png', 'image/gif'}

mime = magic.from_buffer(file_data, mime=True)
if mime not in ALLOWED_TYPES:
    return error('Invalid file type')

Store Files Outside Webroot

# VULNERABLE - files in webroot can be executed
UPLOAD_DIR = '/var/www/html/uploads'  # Dangerous!

# SECURE - files outside webroot
UPLOAD_DIR = '/var/app_data/uploads'  # Not web-accessible

# Serve through application with access controls
@app.route('/file/<file_id>')
def serve_file(file_id):
    # Authenticate user, check permissions
    if not user.can_access(file_id):
        abort(403)

    filepath = get_secure_path(file_id)
    return send_file(filepath)

Rename Uploaded Files

# VULNERABLE - uses original filename
save_path = os.path.join(UPLOAD_DIR, uploaded_filename)

# SECURE - generate random filename
import uuid
extension = get_validated_extension(uploaded_filename)
new_filename = f"{uuid.uuid4()}.{extension}"
save_path = os.path.join(UPLOAD_DIR, new_filename)

# Store mapping in database
db.store_file(user_id, file_id=new_filename, original_name=uploaded_filename)

Implement File Size Limits

# Limit upload size
MAX_FILE_SIZE = 5 * 1024 * 1024  # 5 MB

if len(file_data) > MAX_FILE_SIZE:
    return error('File too large')

Sanitize Image Uploads

from PIL import Image
import io

# Strip metadata and re-encode
image = Image.open(io.BytesIO(file_data))
clean_image = io.BytesIO()
image.save(clean_image, format='PNG')  # Re-encode as PNG

Configure Web Server

# Nginx - never execute files in upload directory
location /uploads/ {
    # Serve files as downloads only
    add_header Content-Disposition "attachment";
    add_header X-Content-Type-Options "nosniff";

    # Disable script execution
    location ~ \.(php|jsp|asp|aspx|cgi)$ {
        deny all;
    }
}

Scan Uploads for Malware

Integrate anti-virus scanning:

  • ClamAV for open-source scanning
  • Cloud services: VirusTotal API, AWS GuardDuty, Azure Defender
  • Quarantine files until scan completes

Dynamic Scan Guidance

For guidance on remediating this CWE when detected by dynamic (DAST) scanners:

Additional Resources