HackTheBox - TheNotebook
Creado

Máquina Linux nivel medio. Exploraremos el mundo de los JSON Web Tokens para ingresar a una web con permisos administrativos. Jugaremos con llaves SSH y romperemos el siempre fiel Docker mediante un CVE que permite sobreescribir el contenido del binario /bin/sh con lo que queramos.
TL;DR (Spanish writeup)
Creada por: mostwanted002.
Se ave cina…
Hola, nos enfrentaremos a un inicio muy lindo. Encontraremos un sitio web que nos permite guardar nuestras notas y nada más :O Pero echándole ojo a la cookie veremos algo interesante, es generada mediante JSON Web Tokens
con una estructura peculiar, la llave con la que lo hace esta siendo llamada mediante una URL y cada usuario lleva indicado si es o no admin, aprovecharemos esto para crear un nuevo token que llame la llave de nuestro servidor y también otorgándole permisos a nuestro usuario de ser admin. Esto para finalmente lograr subir archivos en el Admin Panel yyy claramente subiremos una Web Shell para obtener una sesión en la máquina como www-data
.
Cree un script para automatizar todo este proceso y obtener RCE en la terminal, échenle un ojazo:
Enumerando nos toparemos con las llaves SSH
del usuario noah
, usaremos su llave privada para obtener una sesión en la máquina sin necesitar la contraseña.
Validando los permisos de administrador que tenemos sobre el sistema, nos daremos cuenta de que podemos ejecutar un contenedor con Docker
, validando la versión del propio Docker y buscando vulnerabilidades relacionadas, encontraremos una que nos permite modificar el contenido del binario runC
(que se ejecuta siempre que llamemos a Docker) y que al mismo tiempo modifica el binario /bin/sh
con nuestro payload, para una vez llamemos una Shell con /bin/sh dentro del contenedor se ejecute el payload… Usaremos esto para conseguir una Shell como el usuario root
, a darleeeee.
…
Clasificación de la máquina según la gentesita
Vulns conocidas, le cuesta mucho llegar a ser real (pero lo intenta).
Escribo para tener mis “notas”, por si algun dia 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.
…
Nuestro camino a donde gloria:
…
Enumeración #
Inicialmente haremos un escaneo de puertos para saber que servicios esta ejecutando la máquina:
Parámetro | Descripción |
---|---|
-p- | Escanea todos los 65535. |
–open | Solo los puertos que están abiertos. |
-v | Permite ver en consola lo que va encontrando. |
-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 |
A ver, que tenemos…
Puerto | Descripción |
---|---|
22 | SSH: Acceso a un servidor remoto por medio de un canal seguro. |
80 | HTTP: Servidor web. |
Ahora hagamos un escaneo de scripts y versiones con base en cada servicio (puerto) encontrado, así validamos a profundidad 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:
Puerto | Servicio | Versión |
---|---|---|
22 | SSH | OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 |
80 | HTTP | nginx 1.14.0 |
A darle a ver por donde podemos entrar.
…
Puerto 80 ×
Un lugar para guardar mis notas o pensamientos… Podemos registrarnos e ingresar al sitio, después de este paso, en el dashboard tenemos:
Hagamos caso y veamos las notas: (disculparán el montón de pruebas)
Algo raro que note fue la URL, no sé, poco usual…
🌋 ¡Entramos en un Rabbit hole, cuidaoooo!
Después de un rato enumerando, probando, jugando con la URL, etc. Nada. Haciendo fuzz encontramos un directorio llamado /admin
:
Pero tenemos un código de estado 403
que nos indica la prohibición completa hacia ese recurso :P Pero jugando con ese mismo recurso encontramos otros directorios:
Curioso, tenemos prohibido el acceso al recurso /a, pero no al
/admin/notes`, veamos si podemos obtener algo de ahí:
Jmm, intentando agregar notas siempre obtenemos “Internal Server Error
”, peeero si nos fijamos en la URL, a veces cambia como si hiciera la inserción de la nota, veamos un ejemplo rápidamente:
En la URL se agrega un numero y si validamos si se creó la nota:
Y si, se crea… Pero no logre hacer nada con esto :P
🗻 Salimos del Rabbit hole
Dando vueltas y revisando cositas, nos damos cuenta de algo lindo en nuestra cookie: (podemos verla de varias formas, pero como lo divide el navegador esta bien para que se entienda mejor lo que haremos)
Es un formato que había usado en alguna ocasión y de una me acordé de que trataba (también por el inicio de la cadena (ey
, que en base64
es {"
(o sea el inicio de un JSON e.e))…
JSON Web Tokens, que sirven para transmitir información mediante objetos JSON
de manera segura, esto gracias a que son firmados digitalmente con llaves privadas o públicas.
Entonces, podemos usar la herramienta jwt.io para jugar con estos tokens, así que, tomamos nuestra cookie auth
y la pegamos a la izquierda:
Algo lindo de esta herramienta es que nos separa por colores (y puntos) las partes del token (cada parte del array que nos mostró el navegador). Cada apartado del token esta en base64
, la página nos lo decodea y a la derecha tenemos el resultado… (Todo esto podemos cambiarlo, pero antes veamos que hay en cada apartado)
Header:
Vemos el tipo de token y el tipo de algoritmo usado yyyyy podemos deducir que esta tomando la llave privada servida en el puerto 7070
del localhost llamada privKey.key
.
Payload (Data):
Tenemos info del usuario y un campo extraño que hace alusión a algo sobre el administrador y en nuestro caso esta apagado… Interesante.
Verify signature:
Acá va la llave privada del host.
…
Explotación #
Bien, como podemos aprovecharnos de esto…
Sabemos que esta usando una URL para leer la llave privada que usa contra el JWT
, entonces:
- Generaremos una llave privada.
- hostearemos un servidor web.
- Y en el header pondremos nuestra URL llamando la llave, esto para que la web tome nuestra llave y podamos modificar el token.
En el apartado payload (data) aprovecharemos el ítem que habla del admin
para alterarlo a true
y ver si nos asignan como administradores del sistema de notas.
Y finalmente agregaremos nuestra llave privada en verify signature.
Démosle. Generemos la llave privada, me guie de este recurso:
Header:
Entonces, ahora modificamos el header: (se puede hacer en la Shell o en la web, para que sea más legible se las mostraré en la web)
Lo pasamos a base64
:
Y pegamos en la web (pegado y sin =
)
Payload (data):
Verify signature:
Copiamos la llave que generamos (con todo y --
) y la pegamos en la web en esta parte:
Y listos, tenemos nuestro token generado. Ahora la prueba de fuego.
Hosteamos el servidor web:
Tomamos el token, editamos nuestra cookie auth
por la nueva y simplemente refrescamos la página. Yyyyyyyyyy obtenemos:
¿Ves algo distinto? e.e (Tamos dentro como admin fathEEEEEEer, muy lindo esto)
Veamos el nuevo apartado Admin Panel
:
Opa, podemos subir archivos y parece que de cualquier tipo, probemos a subir de una un archivo .php
para generar ejecución remota de comandos. Veamos si nos da algún problema…
El script simplemente indica: que recibirá una petición mediante el método GET
y la guardara en la variable xmd
, esta a su vez, hará una petición al sistema mediante shell_exec
y el resultado del comando ejecutado se guardará en la variable $coma
, al final simplemente mostramos ese contenido. Subámoslo, seleccionamos el archivo y damos clic en Save
, nos devuelve:
Veamos el archivo que se subió y probemos de una vez por ejemplo, ver que usuario somos y el hostname:
Perfecto, tenemos ejecución remota de comandos, entablémonos una Reverse Shell…
(El archivo es borrado rápidamente así que debemos ser igual o más rápidos)
Probando y fallando podemos generar un archivo .sh
que contenga lo que queramos ejecutar en el sistema y simplemente como comando en la web le decimos que haga un cURL
hacia nuestro script y que a su vez interprete y ejecute el contenido:
Colocamos este archivo en la ruta en que tenemos el servidor de Python
activo, así evitamos servir otro puerto :P
Nos ponemos en escucha con netcat
:
Y lanzamos como payload
para validar que tenemos comunicación y ve nuestro código:
Y simplemente le agregamos | bash
para que interprete el contenido del archivo y lo ejecute en el sistema:
YYYYYYYyyyyYye.e:
Tamos dentro de la máquina, peeeerfectowowowo.
…
Es un poco MEHH el estar creando el token, modificando la cookie y todo eso manualmente, así que aprovechemos la oportunidad para automatizar toooodo y obtener ejecución de comandos simplemente pasándole el comando que queremos ejecutar a un script:
Ahora sí, sigamos.
…
Movimiento lateral www-data -> noah #
Enumerando el directorio /var/backups
encontramos un archivo algo llamativo:
Copiemos el archivo comprimido e intentemos ver su contenido:
Opa, el backup del /home
de un usuario llamado noah
(efectivamente en la máquina existe), tenemos un par de llaves SSH
, copiémonos el contenido de la llave privada (id_rsa)
y creémonos un archivo con su contenido en nuestra máquina, esto para entrar por medio de SSH
sin tener que ingresar contraseña:
Y ejecutamos hacia la máquina:
Nice, somos noah
y tenemos acceso a la flag user.txt
.
…
Escalada de privilegios #
Si vemos los permisos que tenemos como root
, encontramos:
El usuario noah
puede ejecutar comandos inicialmente en el contenedor webapp-dev01
y dado el caso en todos los contenedores que empiecen con webapp-dev01
, veamos que hay dentro, ejecutemos una bash
en el container:
Bien, tenemos la estructura y todos los archivos que usa para el servidor web de las notas… Pero enumerando no encontramos nada relevante, así que regresemos y volvamos a enumerar…
Si vemos la versión actual de Docker obtenemos la 18.06.0-ce
:
Buscando vulnerabilidades sobre ella, encontramos el CVE CVE-2019-5736:
Nos indica que el binario runc
(que se ejecuta cuando «ejecutamos» Docker
) es vulnerable a ser sobreescrito y por consiguiente indicarle comandos para que sean ejecutados como el usuario root
en la máquina host :O
runC
: “command-line tool for spawning and running containers”.
Nice, busquemos referencias de exploits a ver cuál podemos usar:
Tenemos varios, dándole unos ojazos, el más sencillo de entender es el de Frichetten, esta hecho en .go
y solo debemos mover un archivo al sistema, metámosle candela:
Lo clonamos en nuestra máquina y vemos el archivo main.go
, explorándolo simplemente debemos modificar la variable payload
, como prueba haré que nos envíe el resultado del comando hostname
a nuestro listener en nc
:
Bajando un poco (y leyendo el repo) nos indica lo que hará. Tomará el binario /bin/sh
y lo sobreescribirá para que una vez sea ejecutado Docker
este llame el binario runC
y este a su vez sobreescriba el contenido del binario /bin/sh
para que se transforme en el contenido de nuestra variable payload
(o sea el comando hostname
en este caso). Pero veremos la ejecución del payload una vez hagamos el llamado del binario /bin/sh
(que en esta parte ya estaría modificado) en el contenedor (para esto necesitaremos otra Shell con el usuario noah
, para primero ejecutar el binario main
y por segundo ejecutar la explotación (el binario /bin/sh
)).
Listo, guardamos y creamos el binario:
Listos, ahora podemos subirlo al contenedor, creemos un servidor en Python
y de paso pongámonos en escucha con nc
:
En la máquina víctima indicamos:
Acá estuve perdido un rato, ya que no lograba ejecutarlo:
Dándole vueltas, desistí de ese script y usé otros repositorios, pero no conseguía ejecutar NADA (¿ya puedes imaginar por qué?)…
Pues resulta que si hacemos el mismo procedimiento, pero en el directorio /tmp
o incluso en el directorio /opt/webapp
, ahí si me permite ejecutarlo 🙃
Claramente por permisos que no entiendo, pero pues X:
Ejecutamos:
Y se queda a la espera de la ejecución del binario /bin/sh
, así que abrimos la otra sesión con SSH
y volvemos a ejecutar el archivo main
, pero ahora en la nueva Shell indicamos:
Y en la sesión donde esta corriendo el binario main
obtenemos:
Yyyy en nuestro listener:
Perfectoooo, estamos hablando con el host
, entablémonos una Reverse Shell:
- Compilamos.
- Subimos binario
main
al contenedor. - Abrimos otra sesión
SSH
. - Ejecutamos el binario
main
. - Ejecutamos el binario
/bin/sh
en la nueva sesión. - Obtenemos nuestra Reverse Sheeeeeeeeeeeeeeell.
Perfectooooooooooooooooooooooooooo, hacemos tratamiento de la TTY
y procedemos a ver las flags:
…
Linda máquina, me fascino el inicio, por lo tanto me cree un script para que haga toooodo el proceso de cambiar la cookie, subir el archivo yyy ejecutar los comandos (:
Muy loco el tema de Docker
y runc
, y casi muero intentando ejecutar el binario en esa ruta :( Perooo bueno, se solucionó.
Muchas gracias por pasarse y leerse este montón de texto :P Y nada, a seguir rompiendo todo (:
Comments