File Upload Vulnerabilities
Bypass file upload filters in CTF web - extensions, MIME, magic bytes, polyglots, path tricks, and unsafe storage.
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
| Technique | Example | Notes |
|---|---|---|
| Double extension | shell.jpg.php | Parser takes last segment or first - depends on server |
| Alternate extensions | .phtml, .php5, .phar | Apache AddHandler / SetHandler |
| Null byte (legacy) | shell.php%00.jpg | Mostly dead in modern PHP; still appears in CTF |
| Case / Unicode | .PhP, homoglyphs | Weak filters only |
| Trailing chars | shell.php., shell.php | Windows / NTFS stripping |
| Path in name | ../../var/www/html/shell.php | If 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:
| Format | Typical signature (hex) |
|---|---|
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| JPEG | FF D8 FF |
| GIF | GIF87a / GIF89a |
%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 sends | Trustworthy? |
|---|---|
Content-Type: image/png | No - 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=withContent-Disposition: attachmentandX-Content-Type-Options: nosniff
.htaccess / Config Upload
If the upload directory allows Apache overrides and you can upload .htaccess:
AddType application/x-httpd-php .jpgThen .jpg may execute as PHP. CTF challenges love this when the app only checks “extension is .jpg”.
Zip / Archive Uploads
| Risk | What happens |
|---|---|
| Zip slip | Archive entry path ../../etc/cron.d/evil overwrites |
| Zip bomb | DoS - huge nested compression |
| Symlinks | Extract 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
.phpvariants - 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.jpgLast updated on