Weakness reference
CWE-646

Reliance on File Name or Extension of Externally-Supplied File

This weakness occurs when an application trusts a file's name or extension to determine how it should be processed, without verifying the actual file content…

01Summary

This weakness occurs when an application trusts a file's name or extension to determine how it should be processed, without verifying the actual file content. An attacker can rename a malicious file to have a trusted extension (e.g., renaming a PHP script as .jpg) and trick the application into executing or processing it as if it were safe. This can lead to arbitrary code execution, data exposure, or other attacks depending on how the misidentified file is handled.

02How It Happens

Applications often use file extensions as a shortcut to determine file type and processing logic. For example, code might check if a filename ends in .jpg and assume it's a safe image, or check for .pdf and route it to a PDF parser. An attacker can exploit this by uploading a file with a misleading extension while the actual content is malicious—such as a PHP webshell named photo.jpg, or an executable disguised with a document extension. The application processes the file based on its name rather than its actual content, leading to unintended behavior. This is especially dangerous in environments where uploaded files are stored in web-accessible directories or processed by interpreters.

03Real-World Impact

If an attacker successfully uploads a disguised executable or script file, they may achieve remote code execution on the server. In other scenarios, a malicious file might bypass antivirus scanning or security filters that only check extensions. Data theft, site defacement, malware distribution, and lateral movement within a network are all possible outcomes. The severity depends on where uploaded files are stored and what permissions the web server process has.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import os
from werkzeug.utils import secure_filename

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    filename = secure_filename(file.filename)
    
    # Vulnerable: trusts extension only
    if filename.endswith('.jpg') or filename.endswith('.png'):
        file.save(os.path.join('uploads/', filename))
        return "File uploaded safely"
    else:
        return "Invalid file type"

Why it's vulnerable:
The code checks only the filename extension, not the actual file content. An attacker can rename a PHP script to shell.jpg and it will be accepted and stored in a web-accessible directory.

Fixed pattern
import os
import magic
from werkzeug.utils import secure_filename

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    filename = secure_filename(file.filename)
    
    # Fixed: verify actual file content via MIME type
    file_content = file.read()
    file.seek(0)
    mime_type = magic.from_buffer(file_content, mime=True)
    
    allowed_types = ['image/jpeg', 'image/png']
    if mime_type in allowed_types:
        # Regenerate filename to prevent extension-based attacks
        new_filename = f"{uuid.uuid4()}.jpg"
        file.save(os.path.join('uploads/', new_filename))
        return "File uploaded safely"
    else:
        return "Invalid file type"
Vulnerable pattern
<?php
if ($_FILES['upload']['type'] === 'image/jpeg' || 
    pathinfo($_FILES['upload']['name'], PATHINFO_EXTENSION) === 'jpg') {
    move_uploaded_file($_FILES['upload']['tmp_name'], 
                       'uploads/' . $_FILES['upload']['name']);
    echo "File uploaded";
} else {
    echo "Invalid type";
}
?>

Why it's vulnerable:
The code relies on the client-supplied MIME type and the filename extension. Both can be spoofed by an attacker; the actual file content is never checked.

Fixed pattern
<?php
$allowed_types = ['image/jpeg', 'image/png'];
$tmp_file = $_FILES['upload']['tmp_name'];

// Fixed: verify actual file content using finfo
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $tmp_file);
finfo_close($finfo);

if (in_array($mime_type, $allowed_types, true)) {
    // Regenerate filename to prevent extension-based attacks
    $new_filename = bin2hex(random_bytes(16)) . '.jpg';
    move_uploaded_file($tmp_file, 'uploads/' . $new_filename);
    echo "File uploaded safely";
} else {
    echo "Invalid file type";
}
?>

05Prevention Checklist

Verify file content, not just extension.
Use MIME type detection libraries (Python's python-magic, PHP's finfo_* functions) to inspect actual file bytes, not client-supplied metadata.
Regenerate filenames.
Store uploaded files with randomly generated names (UUID, hash) rather than user-supplied names, stripping any extension the attacker provided.
Store uploads outside the web root.
Place uploaded files in a directory that is not directly accessible via HTTP, and serve them through a download script that enforces proper content-type headers.
Implement allowlisting, not blocklisting.
Define exactly which MIME types are acceptable; reject everything else. Do not try to blacklist dangerous types.
Set correct HTTP headers.
When serving uploaded files, explicitly set Content-Type and Content-Disposition headers to control how browsers handle them.
Disable script execution in upload directories.
Configure your web server (Apache, Nginx) to prevent execution of scripts (PHP, CGI, etc.) in the uploads folder.

06Signs You May Already Be Affected

Check your upload directory for files with mismatched extensions and content—for example, a .jpg file that actually contains PHP code or executable content. Review web server logs for unusual requests to your uploads folder, particularly attempts to access files with script extensions or requests that trigger 404 errors followed by successful access to renamed files. Unexpected admin accounts or new files in sensitive directories may also indicate a successful file upload attack.

07Related Recent Vulnerabilities