Athena Wiki

File Upload Vulnerabilities

webintermediate

Bypass file upload filters in CTF web - extensions, MIME, magic bytes, polyglots, path tricks, and unsafe storage.

webfile-uploadunrestricted-uploadwebshellmimemagic-bytespath-traversal

File Upload Vulnerabilities

Unsafe file upload means the application accepts a file without enforcing what it claims to be and where it ends up. If the file lands under a path the web server executes (.php, .jsp, ASP.NET, CGI) or triggers a vulnerable parser (ImageMagick, old FFmpeg), you get RCE or strong read primitives. OWASP classifies the worst cases as Unrestricted File Upload; practical defenses are summarized in the File Upload Cheat Sheet.

In CTF, your job is usually: get a shell or read a flag by uploading something the server mishandles.

Vulnerable Patterns

// BAD: extension from user filename, path from user input
$ext = pathinfo($_FILES['f']['name'], PATHINFO_EXTENSION);
move_uploaded_file($_FILES['f']['tmp_name'], "uploads/" . $_FILES['f']['name']);
# BAD: trust Content-Type from the browser
if request.files["file"].content_type == "image/png":
    save(request.files["file"])

The Content-Type header is attacker-controlled. The original filename is attacker-controlled. Only server-side checks (content, random name, safe directory) count.

Extension & Name Bypasses

TechniqueExampleNotes
Double extensionshell.jpg.phpParser takes last segment or first - depends on server
Alternate extensions.phtml, .php5, .pharApache AddHandler / SetHandler
Null byte (legacy)shell.php%00.jpgMostly dead in modern PHP; still appears in CTF
Case / Unicode.PhP, homoglyphsWeak filters only
Trailing charsshell.php., shell.php Windows / NTFS stripping
Path in name../../var/www/html/shell.phpIf basename() not used

Always inspect how the server derives the stored filename and whether it uses basename().

Magic Bytes & Polyglots

Servers often check first few bytes of the file:

FormatTypical signature (hex)
PNG89 50 4E 47 0D 0A 1A 0A
JPEGFF D8 FF
GIFGIF87a / GIF89a
PDF%PDF

A polyglot is valid in more than one format (e.g. GIF + PHP). You prepend a minimal valid header and append PHP:

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

Whether it executes depends on extension and handler - the header alone is not enough. For format tricks like PNG+ZIP polyglots, see polyglot files.

MIME Type vs Content

What the browser sendsTrustworthy?
Content-Type: image/pngNo - trivially spoofed with Burp

Better checks:

  • Allowlist extension + verify magic bytes + re-encode image (ImageMagick, Pillow) if business allows
  • Store outside web root and serve via /download?id= with Content-Disposition: attachment and X-Content-Type-Options: nosniff

.htaccess / Config Upload

If the upload directory allows Apache overrides and you can upload .htaccess:

AddType application/x-httpd-php .jpg

Then .jpg may execute as PHP. CTF challenges love this when the app only checks “extension is .jpg”.

Zip / Archive Uploads

RiskWhat happens
Zip slipArchive entry path ../../etc/cron.d/evil overwrites
Zip bombDoS - huge nested compression
SymlinksExtract to sensitive path

Tooling: inspect with python -m zipfile -l, or extract in a sandbox.

ImageMagick / Processing Pipeline

ImageTragick-style bugs (historical CVEs) let crafted images run code during convert. If the challenge resizes your upload, you may need an exploit image for that version - check the challenge’s version hints and public PoCs.

Exploitation Checklist

  • Map all upload endpoints (multipart, JSON base64, profile avatar, “report” attachment)
  • Burp: change extension, MIME, and embedded path after intercept
  • Try double extensions and .php variants
  • If only images: polyglot + .htaccess + SVG (if allowed - SVG can embed script in some contexts)
  • Find where files are served from - static URL vs app route
  • If upload path is predictable, try race with Race Conditions
  • grep the app for ImageMagick, ffmpeg, Pillow - processing = extra attack surface

Quick Reference Commands

# Inspect file type (multiple identifiers)
file upload.bin
xxd upload.bin | head

# Exif / metadata (sometimes triggers parsers)
exiftool upload.jpg

# Binwalk for embedded data
binwalk -e upload.jpg

Last updated on

On this page