File Upload Vulnerabilities
Bypassing upload filters to plant a web shell - and how content-type, extension, and magic-byte checks fail.
A file upload looks innocent - a profile picture, a CV, a CSV import. But if the server stores your file somewhere it will execute it, and it doesn't properly check what you uploaded, you can plant a web shell and run commands. Upload bugs are a fast track from "ordinary user" to full remote code execution.
Authorized targets only
Practice on your own lab (DVWA, Juice Shop, a VM you control). Uploading shells to systems you don't own is illegal - the goal here is to understand the failure so you can prevent it.
Why uploads are dangerous
Two things have to be true for an upload to become code execution:
- The server stores your file somewhere reachable (e.g.
/uploads/). - The server will execute files there (e.g. a PHP handler runs
.php).
If both hold and the filter is weak, you win. So the defender's job is to break at least one of those.
The checks - and how each fails
1. Client-side validation (JavaScript)
The weakest "control" of all. If the browser blocks non-images before sending, just send the request directly with Burp Repeater or curl - the server never sees the JS check.
2. Extension blocklist
Blocking .php is a blocklist, and blocklists leak. Alternate executable extensions often still run:
.php5 .phtml .pht .phar (PHP)
.asp .aspx .cshtml (IIS / .NET)
.jsp .jspx (Java)Double extensions (shell.php.jpg, or shell.jpg.php) can slip past naive checks depending on how the server parses the name.
3. Content-Type header
Some servers trust the Content-Type you send. You control that header, so claim your PHP is an image:
curl -F 'file=@shell.php;type=image/png' http://TARGET/upload.php4. Magic-byte / signature check
Servers that sniff the first bytes can be fooled with a polyglot: a file that starts with a valid image signature but also contains code.
The GIF89a header satisfies the signature check; the trailing PHP still executes.
Beyond web shells
Even when you can't get code execution, uploads enable other attacks:
- Stored XSS via an SVG or HTML file that the browser renders.
- Path traversal in the filename (
../../etc/cron.d/evil) to write outside the upload dir. - DoS via huge files or decompression bombs.
Hands-on Lab
Upload Filter Bypass to Web Shell
Defeat a naive upload filter to plant a web shell and get command execution on a simulated server.
The fix: defense in depth
No single check is enough - stack them:
Secure upload design
- Allowlist extensions and MIME types server-side (never blocklist).
- Re-encode images through a trusted library so any embedded payload is destroyed.
- Store uploads outside the web root, or in a directory where script execution is disabled.
- Rename to a random UUID - never trust the user's filename.
- Verify the real type with magic-byte inspection and the extension, and enforce a size limit.
- Serve user files from a separate, sandboxed domain.
Key takeaways
- An upload becomes RCE when the server both stores and executes your file and the filter is weak.
- Client-side checks, extension blocklists,
Content-Type, and magic bytes can each be bypassed individually. - The robust fix is layered: allowlist + re-encode + store outside web root + no execution in the upload directory + UUID filenames.