Unrestricted Upload of File with Dangerous Type

Description

Unrestricted Upload of File with Dangerous Type occurs when software allows users to upload files without properly validating, restricting, or handling the file types. Attackers can upload files with dangerous content such as web shells, scripts, or executables that can be executed on the server. Common attack vectors include uploading PHP, JSP, or ASP files to web servers, uploading executables to be run by other users, or uploading HTML/SVG files containing malicious scripts. This vulnerability provides attackers with direct code execution capabilities on the target system.

Risk

Unrestricted file upload is one of the most severe web application vulnerabilities because it can directly lead to remote code execution. The 2019 Magento breach compromised thousands of e-commerce stores through web shell uploads. The Equifax breach involved file upload exploitation during attack phases. WordPress plugins regularly suffer from this vulnerability, affecting millions of websites. Once attackers upload a web shell, they gain persistent access to execute commands, steal data, pivot to internal networks, and install malware. The impact is often complete system compromise.

Solution

Implement a whitelist of allowed file extensions and reject all others. Validate file content using magic bytes/file signatures, not just extensions. Rename uploaded files to remove original extensions and prevent execution. Store uploads outside the web root or in a location that cannot execute scripts. Use a separate domain for serving uploaded content. Set proper Content-Type and Content-Disposition headers. Implement file size limits. Scan uploaded files for malware. Never rely solely on client-side validation.

Common Consequences

ImpactDetails
Access ControlScope: Remote Code Execution

Uploaded scripts or executables can be executed, giving attackers full server control.
IntegrityScope: System Compromise

Web shells provide persistent backdoor access for data manipulation and system modification.
ConfidentialityScope: Data Breach

Server access enables attackers to steal databases, credentials, and sensitive files.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: No file type validation
<?php
$target_dir = "uploads/";
$target_file = $target_dir . basename($_FILES["file"]["name"]);

// Attacker uploads "shell.php" - gets executed!
move_uploaded_file($_FILES["file"]["tmp_name"], $target_file);
echo "File uploaded successfully";
?>

// VULNERABLE: Extension-only check (easily bypassed)
<?php
$filename = $_FILES["file"]["name"];
$ext = pathinfo($filename, PATHINFO_EXTENSION);

// Attacker uses "shell.php.jpg" or "shell.pHp"
if ($ext == "jpg" || $ext == "png") {
    move_uploaded_file($_FILES["file"]["tmp_name"], "uploads/" . $filename);
}
?>
# VULNERABLE: Trusting user-provided filename
@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['file']
    # Attacker controls filename - could be "../../../var/www/html/shell.php"
    file.save(os.path.join(UPLOAD_DIR, file.filename))
    return 'Uploaded'

Fixed Code

<?php
// SAFE: Comprehensive file upload validation
function secure_upload($file) {
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif'];
    $max_size = 5 * 1024 * 1024;  // 5 MB

    // Check file size
    if ($file['size'] > $max_size) {
        throw new Exception('File too large');
    }

    // Get and validate extension
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed_extensions)) {
        throw new Exception('Invalid file extension');
    }

    // Validate MIME type from content (not user-provided)
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);
    if (!in_array($mime, $allowed_types)) {
        throw new Exception('Invalid file type');
    }

    // Verify magic bytes
    $magic_bytes = file_get_contents($file['tmp_name'], false, null, 0, 4);
    if (!is_valid_image_magic($magic_bytes)) {
        throw new Exception('Invalid file content');
    }

    // Generate random filename (remove original)
    $new_filename = bin2hex(random_bytes(16)) . '.' . $ext;

    // Store outside web root
    $upload_path = '/var/uploads/' . $new_filename;  // Not in web root!
    move_uploaded_file($file['tmp_name'], $upload_path);

    return $new_filename;
}

function is_valid_image_magic($bytes) {
    $signatures = [
        "\xFF\xD8\xFF" => 'jpeg',    // JPEG
        "\x89PNG" => 'png',           // PNG
        "GIF87a" => 'gif',            // GIF87a
        "GIF89a" => 'gif',            // GIF89a
    ];

    foreach ($signatures as $sig => $type) {
        if (strpos($bytes, $sig) === 0) {
            return true;
        }
    }
    return false;
}
?>
import os
import uuid
from werkzeug.utils import secure_filename
import magic  # python-magic library

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
ALLOWED_MIMES = {'image/png', 'image/jpeg', 'image/gif'}
UPLOAD_FOLDER = '/var/uploads'  # Outside web root
MAX_SIZE = 5 * 1024 * 1024  # 5 MB

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/upload', methods=['POST'])
def upload_safe():
    file = request.files['file']

    # Check filename has allowed extension
    if not allowed_file(file.filename):
        return 'Invalid file type', 400

    # Check file size
    file.seek(0, 2)  # Seek to end
    size = file.tell()
    file.seek(0)     # Reset to beginning

    if size > MAX_SIZE:
        return 'File too large', 400

    # Validate MIME type from content
    mime = magic.from_buffer(file.read(2048), mime=True)
    file.seek(0)

    if mime not in ALLOWED_MIMES:
        return 'Invalid file type', 400

    # Generate safe, random filename
    ext = file.filename.rsplit('.', 1)[1].lower()
    new_filename = f"{uuid.uuid4().hex}.{ext}"

    # Save to secure location
    filepath = os.path.join(UPLOAD_FOLDER, new_filename)
    file.save(filepath)

    return {'filename': new_filename}, 200

Exploited in the Wild

Magento E-Commerce Breach (Magento, 2019)

Critical file upload vulnerability in Magento allowed attackers to upload web shells, compromising thousands of online stores. Customer payment data and personal information was stolen.

WordPress Plugin Vulnerabilities (WordPress, Multiple)

Multiple WordPress plugins including WP Statistics have suffered from CWE-434 vulnerabilities, allowing attackers to upload backdoors to millions of websites.

WSO2 Enterprise Integrator (WSO2, 2025)

CVE-2025-1862 in WSO2 Enterprise Integrator 6.6.0 allows administrators to upload arbitrary files through the BPEL uploader due to improper filename validation.


Tools to test/exploit

  • Burp Suite — manipulate file upload requests.

  • Weevely — web shell generation and management.

  • Upload Scanner — automated file upload vulnerability finder.


CVE Examples

  • CVE-2025-1862 — WSO2 Enterprise Integrator arbitrary file upload.

  • CVE-2024-27198 — JetBrains TeamCity authentication bypass enabling file upload.

  • CVE-2023-50164 — Apache Struts path traversal file upload RCE.


References

  1. MITRE. "CWE-434: Unrestricted Upload of File with Dangerous Type." https://cwe.mitre.org/data/definitions/434.html

  2. OWASP. "Unrestricted File Upload." https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload