Weakness reference
CWE-434

Unrestricted Upload of File with Dangerous Type

This weakness occurs when a web application accepts file uploads without properly validating the file type, allowing an attacker to upload executable scripts…

01Summary

This weakness occurs when a web application accepts file uploads without properly validating the file type, allowing an attacker to upload executable scripts, archives, or other dangerous files. If the server processes or executes these files, an attacker can gain remote code execution, deface content, or compromise the entire system. Proper file type validation is essential to prevent this risk.

02How It Happens

Applications often allow users to upload files—profile pictures, documents, media—without adequately checking what is actually being uploaded. An attacker can rename a PHP script to have a .jpg extension, or upload a .zip archive containing executable files, or use MIME type spoofing to bypass weak client-side checks. If the server stores these files in a web-accessible directory and executes them (or auto-extracts archives), the attacker's code runs with the server's privileges. The vulnerability is compounded when validation relies only on file extension, MIME type headers (which are user-controlled), or client-side checks that can be bypassed.

03Real-World Impact

Successful exploitation typically leads to remote code execution, allowing an attacker to read sensitive files, modify or delete data, install malware, or pivot to other systems on the network. In content management systems, attackers can upload malicious plugins or themes. In document management systems, they can upload archives that extract to overwrite legitimate files. The impact ranges from site defacement to complete server compromise, depending on file permissions and server configuration.

04Vulnerable & Fixed Patterns

Vulnerable pattern
from flask import Flask, request
import os

app = Flask(__name__)
UPLOAD_DIR = '/var/www/uploads'

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    filename = file.filename
    file.save(os.path.join(UPLOAD_DIR, filename))
    return 'File uploaded successfully'

Why it's vulnerable:
The code accepts any filename and saves it directly to a web-accessible directory without checking the file type, extension, or content. An attacker can upload shell.php or malware.exe and execute it.

Fixed pattern
from flask import Flask, request
import os
import magic

app = Flask(__name__)
UPLOAD_DIR = '/var/www/uploads'
ALLOWED_TYPES = {'image/jpeg', 'image/png', 'application/pdf'}
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.pdf'}

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    filename = file.filename
    
    # Validate extension
    _, ext = os.path.splitext(filename)
    if ext.lower() not in ALLOWED_EXTENSIONS:
        return 'Invalid file type', 400
    
    # Validate MIME type by reading file content
    mime = magic.from_buffer(file.read(1024), mime=True)
    if mime not in ALLOWED_TYPES:
        return 'Invalid file type', 400
    
    file.seek(0)
    file.save(os.path.join(UPLOAD_DIR, filename))
    return 'File uploaded successfully'
Vulnerable pattern
<?php
$upload_dir = '/var/www/uploads/';
$file = $_FILES['file'];

// Only check extension
$filename = $file['name'];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if ($ext === 'jpg' || $ext === 'png') {
    move_uploaded_file($file['tmp_name'], $upload_dir . $filename);
    echo 'File uploaded';
}
?>

Why it's vulnerable:
Extension-only validation is trivial to bypass—an attacker can rename shell.php to shell.php.jpg or use null-byte injection. The MIME type from $_FILES['type'] is also user-controlled and unreliable.

Fixed pattern
<?php
$upload_dir = '/var/www/uploads/';
$allowed_types = ['image/jpeg', 'image/png', 'application/pdf'];
$allowed_exts = ['jpg', 'jpeg', 'png', 'pdf'];
$file = $_FILES['file'];

$filename = basename($file['name']);
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

// Check extension
if (!in_array($ext, $allowed_exts, true)) {
    die('Invalid file type');
}

// Check MIME type by reading file content
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);

if (!in_array($mime, $allowed_types, true)) {
    die('Invalid file type');
}

// Generate safe filename
$safe_name = bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($file['tmp_name'], $upload_dir . $safe_name);
echo 'File uploaded';
?>

05Prevention Checklist

Maintain an allowlist of safe file types
(by MIME type and extension) rather than a blocklist; reject everything else by default.
Validate file content, not just headers.
Use file magic number inspection (e.g., finfo_file() in PHP, python-magic in Python) to verify the actual file type, not just the extension or Content-Type header.
Store uploads outside the web root
or in a directory configured to not execute scripts (e.g., via .htaccess or web server configuration).
Rename uploaded files
to remove user-controlled names and prevent directory traversal or extension-based attacks.
Set appropriate file permissions
so uploaded files are not executable and cannot be modified by other users.
Scan uploaded files with antivirus or malware detection
if handling user-supplied documents in sensitive environments.

06Signs You May Already Be Affected

Check your upload directories for unexpected file types (e.g., .php, .exe, .sh files in a directory meant for images). Review web server logs for requests to unusual file paths or suspicious file access patterns. Look for unexpected admin accounts or new files in system directories that you did not create. If you find evidence of unauthorized file uploads, isolate the affected system and review access logs to determine the scope of compromise.

07Related Recent Vulnerabilities