{
    "slug": "arbitrary_file_upload",
    "term": "Arbitrary File Upload",
    "category": "security",
    "difficulty": "intermediate",
    "short": "Accepting file uploads without validating type, extension, and content can allow PHP shell uploads and RCE.",
    "long": "Unrestricted file upload lets attackers upload executable scripts (e.g. a PHP web shell) disguised with a harmless extension or MIME type. If stored in a web-accessible directory, the shell can be executed directly, giving full server access. Mitigation requires validating the actual file content with mime_content_type(), enforcing an extension allowlist, storing uploads outside the web root, and using random server-generated filenames.",
    "aliases": [
        "unrestricted file upload",
        "webshell upload"
    ],
    "tags": [
        "owasp-top10",
        "injection",
        "rce",
        "cwe-434"
    ],
    "misconception": "Checking the file extension is sufficient validation. Attackers bypass extension checks with double extensions (.php.jpg), null bytes, and polyglot files that are simultaneously valid images and valid PHP.",
    "why_it_matters": "An attacker who uploads a PHP file to a web-accessible path has remote code execution — effectively full server compromise.",
    "common_mistakes": [
        "Trusting the MIME type or Content-Type header sent by the client — both are attacker-controlled.",
        "Checking only the file extension but not actual file content (magic bytes).",
        "Storing uploaded files inside the webroot where the server will execute them.",
        "Using the original filename from the upload without sanitisation, enabling path traversal."
    ],
    "when_to_use": [
        "Validate file type by reading magic bytes (finfo_file) rather than trusting the client-supplied MIME type or extension.",
        "Store uploaded files outside the web root or in object storage — never in a directory the web server can execute.",
        "Rename uploaded files to a random slug on save — never use the original filename in the stored path."
    ],
    "avoid_when": [
        "Never allow uploads to a directory with PHP execution enabled — even a validated image can be re-requested as a PHP file if stored incorrectly.",
        "Do not use $_FILES[\"type\"] for validation — it is supplied by the client and trivially spoofed.",
        "Avoid serving uploaded files through the same domain as the application without a Content-Disposition: attachment header."
    ],
    "related": [
        "lfi",
        "path_traversal",
        "mime_content_type"
    ],
    "prerequisites": [
        "input_validation",
        "allowlist_vs_blocklist",
        "mime_content_type"
    ],
    "refs": [
        "https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload",
        "https://cwe.mitre.org/data/definitions/434.html"
    ],
    "bad_code": "// No validation — attacker uploads a PHP webshell\nmove_uploaded_file($_FILES['f']['tmp_name'], 'uploads/'.$_FILES['f']['name']);",
    "good_code": "\\$upload = \\$_FILES['file'];\n\n// 1. Verify actual MIME type via finfo (not browser-supplied Content-Type)\n\\$finfo   = new \\finfo(FILEINFO_MIME_TYPE);\n\\$mime    = \\$finfo->file(\\$upload['tmp_name']);\n\\$allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];\nif (!in_array(\\$mime, \\$allowed, true)) abort(415);\n\n// 2. Random filename — never trust original name\n\\$ext      = ['image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp','image/gif'=>'gif'][\\$mime];\n\\$filename = bin2hex(random_bytes(16)) . '.' . \\$ext;\n\n// 3. Store outside webroot — never in a PHP-executable directory\nmove_uploaded_file(\\$upload['tmp_name'], '/var/uploads/' . \\$filename);\n\n// 4. Serve via a controller that sets correct headers\nheader('Content-Type: ' . \\$mime);\nreadfile('/var/uploads/' . \\$filename);",
    "example_note": "The bad example moves the file with the original user-supplied name to a web-accessible uploads directory — an attacker uploads shell.php and executes arbitrary code. The fix validates magic bytes, renames the file, and stores it outside the web root.",
    "quick_fix": "Validate MIME type server-side with finfo, check extension against allowlist, store outside webroot, serve via controller not direct URL",
    "severity": "critical",
    "effort": "medium",
    "created": "2026-03-15",
    "updated": "2026-03-31",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/arbitrary_file_upload",
        "html_url": "https://codeclaritylab.com/glossary/arbitrary_file_upload",
        "json_url": "https://codeclaritylab.com/glossary/arbitrary_file_upload.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Arbitrary File Upload](https://codeclaritylab.com/glossary/arbitrary_file_upload) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/arbitrary_file_upload"
            }
        }
    }
}