HackTheBox - Sink
Creado

Máquina Linux nivel desquiciado. Nos enfrentaremos a un HTTP Request Smuggling
(loco loco), saltaremos entre usuarios aún más locos, veremos commits relacionados a pasos a producción y pruebas extrañas con 🔑🔑, jugaremos bastante con AWS CLI
, encontraremos secretos :O y finalmente desencriptaremos un archivo también jugando con AWS
y llaves KMS
.
TL;DR (Spanish writeup)
Creada por: MrR3boot.
Desquiceddd! Este writeup es largito (más que nada por los bloques de código), así que cafecito y a rompernos la cabeza…
Muy linda máquina.
Nos encontraremos con dos servicios, uno corriendo Gitea
y otro Gunicorn
, en el camino nos veremos las caras con un HTTP request smuggling
el cual nos permitirá interceptar la cookie de sesión del usuario admin@sink.htb
. La usaremos para entrar en un panel y encontrar unas notas, cada una tiene una credencial, una de ellas nos permitirá entrar al servicio Gitea
referenciado antes.
Veremos unos repositorios, commits y demás info. En uno de los commits el usuario marcus
estaba haciendo pruebas con su llave SSH
privada y nos dejó el rastro. Usaremos esa llave para entrar en la máquina como él.
Empezaremos a jugar con AWS CLI
para ver logs y secretos, en el jugueteo :o encontraremos otras credenciales guardadas como eso, secretos. Una de ellas pertenecen al usuario david
y nos permitirán generar una sesión como él tanto en Gitea
como en la máquina.
David en sus archivos tiene uno llamado servers.enc
y esta encriptado mediante aws
. Seguiremos jugando con AWS-CLI
, pero ahora con kms
para interactuar con keyId
s y buscar la manera de desencriptar el archivo validándolo contra distintas llaves que iremos encontrando.
Finalmente encontraremos una llave que nos devuelve una cadena en base64
, la tomamos y guardamos en un archivo, el tipo de archivo generado es un comprimido gzip
, usaremos zcat
para descubrir que contiene el archivo servers.yml
, veremos otras credenciales en este caso de un usuario llamado admin
. Nos servirán para generar una Shell como el usuario root
.
…
Clasificación de la máquina según la gentesita
Muuuuuuuuuy real, alguna que otra cosita conocida pero sobre todo demasiada enumeración (mucha lectura y búsqueda).
Escribo para tener mis “notas”, por si algun día se me olvida todo, leer esto y reencontrarme (o talvez no) :) además de enfocarme en plasmar mis errores y exitos (por si ves mucho texto), todo desde una perspectiva más de enseñanza que de solo mostrar lo que hice.
…
Vuelve que el dolor me mata.
…
Reconocimiento #
…
Usamos nmap para descubrir puertos abiertos 📌
Realizaremos un escaneo de puertos para saber que servicios esta corriendo la máquina:
Parámetro | Descripción |
---|---|
-p- | Escaneamos todos los 65535 puertos. |
–open | Solo los puertos que estén abiertos. |
-v | Permite ver en consola lo que va encontrando (verbose). |
-oG | Guarda el output en un archivo con formato grepeable para usar una función extractPorts de S4vitar que me extrae los puertos en la clipboard |
Perfecto, nos encontramos los puertos y servicios:
Puerto | Descripción |
---|---|
22 | SSH: Tenemos la posibilidad de obtener una Shell de manera segura. |
3000 | PPP: No lo sabemos aún. |
5000 | UPnP: Conjunto de protocolos para la comunicación de periféricos en la red. |
Hagamos un escaneo de scripts y versiones con base en cada servicio (puerto), con ello obtenemos información más detallada de cada uno:
Parámetro | Descripción |
---|---|
-p | Escaneo de los puertos obtenidos. |
-sC | Muestra todos los scripts relacionados con el servicio. |
-sV | Nos permite ver la versión del servicio. |
-oN | Guarda el output en un archivo. |
Obtenemos:
Bien, tenemos varias cositas:
Puerto | Servicio | Versión |
---|---|---|
22 | SSH | OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 |
3000 | PPP (Al parecer HTTP) | - |
- Tiene varias referencias hacia
Gitea
. - Vemos 2 cookies y una relacionada a
gitea
.
Puerto | Servicio | Versión |
---|---|---|
5000 | HTTP | Gunicorn 20.0.0 |
Nada más por ahora, así que empecemos a validar cada servicio y ver por donde podemos jugar…
…
Enumeración #
…
Recorremos el puerto 3000 📌
Listos, confirmamos el servicio Gitea
.
🦠 Básicamente nos permite alojar control de versiones usando Git
y es un fork (copia) de Gogs (que nos ayuda a correr nuestro propio servicio Git
, mejor dicho, tener nuestro propio GitHub), pero mejorado y para toda la familia.
Vale, entonces enumeremos a ver que sacamos…
Si vamos al apartado explore
tenemos 3 ítems, veamos users
:
- Usuario:
david
. - Usuario:
marcus
. - Usuario:
root
. - Versión Gitea:
1.12.6
. - Versión Go:
1.14.12
.
Validando cada usuario, vemos que todos están asociados a una organización, Sink_Solutions
, en Organizations la encontramos:
También podemos logearnos, probando con los usuarios y posibles contraseñas no conseguimos nada…
Buscando vulnerabilidades con las versiones relacionadas encontramos una que posiblemente (no creo :P) esté relacionada, pero debemos estar autenticados, guardémosla por si algo:
…
Recorremos el puerto 5000 📌
🦄 Gunicorn
(Green Unicorn) is a Python WSGI HTTP Server for UNIX. It’s a pre-fork worker model compatible with various web frameworks, simple and lightweight server. musyokaian.
Pero khe jeso de WSGI, rápidamente:
⚙️ WSGI
permite que programas hechos en Python puedan comunicarse a través del protocolo HTTP sin ningún tipo de framework o librería. codigofacilito.
Tenemos un login panel, pero también nos podemos registrar, démosle…
- Tenemos un correo:
admin@sink.htb
.
Bien, pa que lo sepamos:
En el apartado notes
nos permite agregar, ver y borrar notas:
Son notas que cree para probar algun tipo de injección o brecha. Pero por el momento nada…
…
Explotación #
…
Encontramos un vector de ataque muuuy potencial 📌
Bueno bueno bueeeeeeeeeeno…
Después de dar vueltas por las páginas con BurpSuite interceptando las peticiones del servidor http://10.10.10.25:5000
, notamos algo llamativo:
Esta usando un proxy llamado HAProxy
entre las peticiones, pues veamos que se trata:
🚇 HAProxy
es un balanceador de cargas (load balancer: transfiere peticiones entre host para evitar colapsos y hacer que sean procesadas más rápido) entre servidores.
- YT - Balanceador de carga con HAProxy.
- HAProxy en Wikipedia.
- Balanceadores de carga, mejora el rendimiento de tu web.
Teniendo esto claro, validemos si existen vulnerabilidades hacia ese servicio…
Inicialmente nos encontramos con el CVE CVE-2020-11100, el cual apoyado del protocolo HTTP/2
(para hacer un uso más eficiente de los recursos en la red) puede permitirle al atacante enviar una petición “especial”, que puede generar un heap-based buffer overflow
y finalmente una ejecución remota de comandos en el sistema.
Es una vulnerabilidad descubierta por Felix Wilhelm
(integrante del grupo de hackers de Google (Project Zero
)), en el blog oficial de HAProxy
nos redireccionan al writeup creado por él:
- HAProxy Security Update HTTP/2 HPACK - haproxy.com.
- HAProxy: out-of-bounds-write in HTTP/2 - bugs.chromium.org.
Dándole vistazos a otros recursos y temas relacionados a esa vuln, no logre interactuar con ella…
…
Buscando y buscando encontré un PoC en formato de video explotando una vulnerabilidad llamada:
Y que afecta a HAProxy
, esta tiene relacionado el CVE CVE-2019-18277.
Antes de probar la vuln, entendamos (o intentémoslo) sobre HTTP Request Smuggling
.
¿Qué es un HTTP Request Smuggling? (Descubrámoslo) 🪕
Como indica Busra Demir en su artículo, HTTP request smuggling
es una técnica la cual interfiere en el proceso por el que pasan las peticiones del front al back. Donde el atacante puede modificar la petición para incluir otra en la misma petición. Lo que pasara es que ejecutara la primera petición normalmente, pero al terminar de procesarla ejecutara la segunda que tenemos incrustada, logrando así el éxito de la vulnerabilidad.
Esto se logra modificando/agregando 2 headers HTTP:
Pero ¿para qué nos sirve esto? Como explica portswigger nos puede permitir bypassear controles de seguridad, obtener acceso a información sensible yyyy comprometer otros usuarios que estén en la aplicación.
…
Ahora sí, sigamos y probemos si nuestra versión es vulnerable a HTTP Request Smuggling
…
Siguiendo algunos ejemplos de referencias anteriores y de este artículo, logramos obtener la versión de HAProxy
que esta usando el servidor:
Si enviamos la siguiente petición:
- Enviamos la data en formato
chunked
(separada por\n
(newline)). - El total de la traza serian
9
caracteres (note=hola
). - Con
0
le indicamos el final de la data en formatochunk
.
Obtenemos la versión concreta de HAProxy:
HAProxy Version 1.9.10
Bueno, pues podemos enfocarnos un poco más. Quizás esta sea la ruta adecuada para la explotación. Sigamos validando si podemos explotar la web mediante el smuggling…
En las referencias del CVE, encontramos este PoC:
Dándole unas vueltas nos indica algo necesario para la correcta explotación:
The backend must also support HTTP keep-alive.
Así que debemos cambiar el header Connection: close
a Connection: keep-alive
.
Démosle a la locura, modifiquemos la petición incrustando otra solicitud a ver que recibimos:
Y validamos el apartado /notes
, tenemos:
Nada :P 😂
…
Logramos la explotación del HTTP Request Smuggling 📌
Revisando de nuevo el post, nos dice que muchas veces el chunked
no es tomado y debemos agregarle al inicio de ese header la cadena \x0b
(hexadecimal) para que lo interprete.
Después de jugar con él, intentando agregarlo a la petición, obteníamos lo mismo. Peeeeeeero era porque lo estaba haciendo mal.
Si pasamos el valor hexadecimal a base64 externamente (nosotros mismos) y después en Burp usamos el convertidor interno de base64 al valor original, ahí si logramos ver el \x0b
reflejado:
- Pasar
0b
a base64:Cw==
. - Pegar
Cw==
en la petición, seleccionarlo y decodearlo de base64 a valor original:
Seleccionamos la cadena Cw==
y hacemos:
Y obtenemos:
Perfecto, validemos si cambia algo ahora…
Nos genera dos notas, una esta vacía y la otra llenita :P Veamos la llenita claramente:
Vale vale valeeeeeeeeeee, que es esta locuraaaaaaaaaaaa… Tenemos una nueva cookie
de algún usuario (o pues al menos es diferente a la nuestra) corriendo el servicio sobre el localhost
.
Viendo lo que tenemos podemos intuir que esta pasando:
(Esto puede sonar enrredado (supongo) pero es lo que entiendo que paso)
🚨 Normalmente (como vimos en las explicaciones anteriores) queremos ingresar a algún recurso al que no tengamos acceso. Como en este caso no sabemos a cuál, lo lanzamos contra el mismo recurso. Vemos que se efectúa nuestro intento, logrando así interceptar la otra petición, pero obteniendo la respuesta en el mismo recurso que usamos para enviarla (o sea, en /notes
)
La petición es un delete
a la nota 1234 (/notes/delete/1234
) pero ejecutada desde el localhost
por el puerto 8080
y claramente por un usuario interno, lo sabemos por qué obtenemos una Cookie
distinta a la nuestra. Podemos usarla para cambiar la que tenemos por esa, recargar la página y ver con quien estamos (si es que cambiamos a otro usuario)…
Pero si notamos el tamaño de la cookie
es más corto (la comparamos con la nuestra), agreguémosle más buffer, simplemente cambiando el valor de la cabecera a Content-Length: 250
por ejemplo y veamos la respuesta ahora:
(Justo acá reiniciaron la máquina, pero pues lo unico que cambiaran seran las cookies, tanto la mia como la que obtengamos)
La respuesta que tenemos en la nueva nota es:
Si ponemos más de 308 como tamaño volvemos a obtener solo una nota, entonces podemos borrar el contenido de note=
(para liberar espacio) y coloquemos Content-Length: 303
(después de algún tanteo):
Y obtenemos ahora si en la nota:
Perfecto, ahora si es del tamaño adecuado :)
(Volvieron a reiniciar la máquina😐)
Tomemos la cookie. Yo usare la extensión de Firefox llamada Cookie-Editor
(que nos permite jugar con las cookies claramente :P)
Añadimos una nueva cookie, le ponemos de nombre session
y pegamos la cookie que encontramos y damos clic en guardar (add
). Ahora simplemente recargamos la página y estaríamos dentro como el usuario admin@sink.htb
:
…
Encontramos cositas siendo el usuario admin 📌
Si revisamos las notas tenemos 3:
El contenido de cada una es el siguiente:
Opa, tenemos 3 nuevas URL (una aparentemente con certificado SSL), con sus respectivos usuarios y contraseñas… Agreguémoslas al /etc/hosts
e inspeccionemos…
Pero al colocarlos en la web, ninguno redirecciona a ningún sitio. Y pues tiene sentido, no sabemos en qué puerto están corriendo por lo tanto no encuentra realmente lo que tiene que resolver… Pero pues tenemos credenciales y si recordamos, hay un servicio sobre el puerto 3000
(Gitea) con panel login (y en el cual uno de los usuarios era root
), intentemos usar la contraseña que tenemos de root
sobre ese login:
Yyyyy:
Perfecto, tamos dentro, ahoraaaaaaaaaaaaaaaaaaa a enumerar :P
Enumeramos el servicio Gitea como el usuario root 📌
Nos encontramos con 4 repositorios (aunque solo se vean 3 en la imagen hay 4):
O sea que tenemos:
Revisando cada uno, sus respectivos commits y contenido encontramos esto:
- El usuario
marcus
es el que hace los push (sube los cambios),root
simplemente crea los repos.
Cositas relevantes de cada repo:
…
🪕 Log_Management (commits)
Si entramos en ese commit, tenemos el access key ID
y la secret access key
de AWS
(Amazon Web Services):
Podemos tenerlo en cuenta (además del puerto 4566
sobre el `lo), ya que en el siguiente commit esos valores son remplazados:
…
🪕 Key_Management (commits)
Ahora entremos en ese commit en concreto:
Nos encontramos con la llave privada de un usuario (posiblemente de marcus
) guardada en el archivo .keys/dev_keys
.
En el mismo commit vemos como usa la llave mediante el objeto ec2.php
:
Y para pasar a producción cambian el archivo dev_keys
por prod_keys
:
…
Bueno, pues probemos a copiarnos esa key, pasarla a un archivo, darle los permisos necesarios (chmod 600 <file>
) e intentar acceder mediante SSH
con alguno de los usuarios, inicialmente con marcus
que fue el que hizo el push:
En los demás repos no encontre nada realmente relevante :s
Yyyyy estamos dentroooooooooooooooooo:
Niceeeeeeeeeeeeeeeeeee. LOCO LOCOOOOOOOOOOOOOOo lo del smuggling.
Ahora si, quien sabe que nos espere :o
…
AWS Secrets: marcus -> david #
Si enumeramos servicios recordamos al puerto que habíamos visto antes, el 4566
:
Además de muchos otros pero sobre una IP diferente que me recordo a Docker
, enumeremos el servicio docker
a ver que encontramos:
Opa, vemos que esta corriendo varios contenedores, al inicio tenemos al que estamos buscando:
Bien, sabemos que es un contenedor. Validemos que esta corriendo sobre él:
Esta respuesta me acordó a la máquina:
Spoiler: Nombre de la otra máquina
Bucket
En la que también jugábamos con AWS
y contenedores.
Podemos hacer dos cosas, un Remote Port Forwarding
(redireccionamiento de puertos) y validar con nmap
si encontramos algo y además hacer algo de fuzzing
para ver si hay otras rutas… O podemos volver a hablar de la máquina (del spoiler) y recordar que en el fuzzing hecho allá, obteníamos la ruta /health
que nos sirve para validar el rendimiento y disponibilidad de los recursos de AWS.
Entonces podemos hacer una petición ahora junto al /health
y ver que servicios (y su estado) esta corriendo AWS
:
Perfectowowo e.e Pues tenemos 3 servicios activos y corriendo:
Y podemos apoyarnos del API
de Amazon Web Services (aws)
para jugar con lo que encontramos y ver si podemos sacar algo importante:
Probemos con logs primero, intentemos ver la descripción de los grupos de logs creados:
Si ejecutamos ese comando nos pide:
Si recordamos en Gitea
habíamos encontrado un commit que tenía esta información, busquémosla y pongámosla acá…
(Aunque para comprobar el funcionamiento coloque primero cualquier valor en los dos y aun así me trajo la información, así que no es necesaria esta config (supongo))
Y si ejecutamos de nuevo:
Listones, si jugamos así con algunos parámetros podremos ir descubriendo info.
Ahora veamos algo de secretsmanager:
Obtenemos 3 plataformas:
- Jenkins Login: Master Server to manage release cycle 1.
- Sink Panel: A panel to manage the resources in the devnode.
- Jira Support: Manage customer issues.
Si intentamos ver alguna data en concreto (los secretos) podemos hacerlo usando el ID, que sería el valor "ARN"
.
Por ejemplo, veamos el valor secreto de Jenkins Login (get-secret-value
):
🛹 Jenkins Login.
Opa, el valor secreto son unas credenciales:
john@sink.htb
:R);\\)ShS99mZ~Bj
oR);\)ShS99mZ~Bj
Antes de ver si son funcionales, validemos con los otros 2 IDs para ver que tienen:
🛹 Sink Panel.
albert@sink.htb
:Welcome123!
🛹 Jira Support.
david@sink.htb
:EAL8=bcC=
a7f2#k`
Acá david
es interesante porque lo tenemos presente como usuario de Gitea
y también como usuario de la propia máquina. Validemos en el panel login de Gitea
estas credenciales:
Son funcionales. Probemos si podemos hacer reutilización de contraseñas e intentemos migrarnos a david
pero desde el sistema:
Perfectoooooooooooooooooooooooooooooooooooo, somos david
ahora (:
Antes, veamos si hay algo importante usando kms
con AWS CLI
:
Tenemos unas keys, no sé si sean relevantes, pero pues para tenerlas en cuenta, de los otros parámetros no podemos obtener algo. Sigamos…
…
Escalada de privilegios #
En el /home
de david
tenemos un par de carpetas que nos llevan a un objeto.enc
:
Intentando crackearlo no hacemos nada :P Veamos que podemos relacionar para leer el archivo.
Bueno, buscando encontramos como se pudo haber generado el archivo mediante aws
:
Podemos ver que para generar el archivo se usa un keyId
yyyyyyy anteriormente encontramos varios keyId
s. Tengamos esto presente…
No contamos con aws
, pero mediante kms
tenemos varias funciones para jugar con los keyId
, una llamada decrypt:
Entonces, según la documentación el argumento obligatorio seria:
--ciphertext-blob fileb://
: Que ahí le indicamos la ruta del archivo.enc
Podemos intentar desencriptar el archivo servers.enc
:
Pero nada, solo vemos errores, entiendo que debemos indicarle la keyId
, elijamos cualquiera y veamos que sucede:
Nos indica que la operación Decrypt
esta inhabilitada para esa key
, leyendo de nuevo la doc de kms hay un argumento para habilitar una key
, probemos a ver si de eso se trata el error:
Obtenemos un error diferente, relacionado posiblemente a la desencriptación en concreto ( Invalid Ciphertext
) o quizás a que esa key
no es la necesaria para ese archivo…
Creémonos un script para que nos valide con todas las keys.
Las extraemos:
En el script las guardaremos en un archivo temporal para ir leyendo cada una, ahora si hagamos el archivo:
- Agregaremos los argumentos
--output text
y--query Plaintext
(These parameters extract the decrypted data, called the plaintext, from the command’s output.)
Pero no obtenemos nada…
Probemos a jugar con los métodos de encriptación:
Entonces adecuando esto a nuestro script quedaría:
Ejecutamos yyyy:
Opaaaaaaa, logramos desencriptar el archivo y ver el contenido, tenemos una cadena en base64
:
- La llave:
804125db-bdf1-465a-a058-07fc87c0fad0
. - El tipo de algoritmo:
RSAES_OAEP_SHA_256
.
Si intentamos decodear esa string obtenemos:
No obtenemos nada legible, pero de una vez pensé en que posiblemente sea data de algún archivo, entonces tomemos el resultado del decode y guardémoslo en un archivo y veamos que tipo de archivo es:
Bien, un archivo comprimido, descomprimámoslo:
Jmmm, buscando en internet sobre este error, encontramos un foro donde alguien recomienda usar zcat
:
¿Pero por qué zcat
?
📁 Normalmente, los archivos comprimidos con gzip
se pueden restaurar a su forma original con los comandos gzip -d
o gunzip
. ¿Qué sucede si desea ver el contenido de un archivo comprimido sin descomprimirlo? Para este propósito, necesita la utilidad zcat
. linux-console
📂 zcat
will uncompress files that have the correct magic number whether they have a .gz
suffix or not. linuxquestions.org
Entonces, si probamos ahora con zcat
:
Eaaa, tenemos un archivo .yml
, donde en su contenido nos encontramos con unas credenciales del usuario admin
. Probémoslas contra el usuario root
en la máquina:
Somos roooooooooooooooooooooooooooooooooooooooot (bueno, veamos si realmente lo somos):
SÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ somos administradores del sistemaaaaaaaaa (:
Solo nos quedaría ver las flags…
…
¡Qué locura eh!
Me gusto demasiado la máquina (: Lo que más me dejo loco fue el HTTP Request Smuggling
.
Que bonito fue este camino. el jugar con aws
de esa manera, increíble. Y nada, como siempre, muchas gracias por leer (este si fue gigante)…
Yyyy a seguir rompiendo todo (:
Comments