Solución de Fetch the Flag CTF 2022: Moongoose
Jason Lynch
9 de noviembre de 2022
0 minutos de lectura¡Gracias por jugar Fetch con nosotros! Felicitaciones a los miles de jugadores que participaron en Fetch the Flag CTF. ¡Y muchísimas gracias a los Snykers que crearon, probaron y documentaron los desafíos!
Como empleado de Snyk, tuve la oportunidad de participar con mis compañeros en una «prueba beta» de la competencia Fetch the Flag 2022 de Snyk. Nos divertimos mucho resolviendo estos desafíos juntos y, personalmente, aprendí mucho de la experiencia. Esta es la solución de un desafío que disfruté especialmente: Moongoose.
Desafío
Si creías que habían puesto un ganso en la luna
Este desafío comienza en una página de inicio de sesión críptica, con temática de la luna y los gansos. Para encontrar la bandera, tendremos que explotar varias vulnerabilidades: recorrido de directorios, inyección NoSQL y algunos comportamientos complicados de JavaScript. Como en cualquier otro desafío CTF, nuestro primer paso es encontrar un punto de entrada.
Guía paso a paso
Cómo encontrar un punto de entrada
Quizás notaste que el nombre de este desafío, «Moongoose», se diferencia por una sola letra de «Mongoose», que es el nombre de un framework popular de Node.js para MongoDB. ¿Será una pista para encontrar la solución? Veamos si podemos confirmar esa sospecha.
Primero, vayamos al desafío y veamos qué observamos a primera vista. Encontramos:
Un formulario de inicio de sesión
Un formulario de registro
Dos imágenes con una luna y un ganso que giran con mucho estilo
Un fondo genial con un campo de estrellas
Cualquier elemento que acepte datos del usuario es un buen punto de partida para el análisis, así que primero nos centraremos en los formularios de inicio de sesión y registro.

Al probar distintas combinaciones de nombres de usuario y contraseñas en cada formulario, obtenemos dos mensajes de error diferentes:
Cuando completamos tanto el nombre de usuario como la contraseña, obtenemos:
{ "☾ 𓅬": "invalid moongoose!" }Cuando falta uno o ambos campos, obtenemos:
{"☾ 𓅬":"invalid moongoose: geese have credentials"}
Curiosamente, vemos el mismo comportamiento en los formularios de inicio de sesión y registro. Abramos el panel de red de las herramientas para desarrolladores del navegador para entender mejor qué sucede.

En estas capturas uso Firefox, pero los mismos principios se aplican a todos los navegadores principales.
Al observar el tráfico de red, vemos que ambos formularios envían la misma solicitud POST al endpoint api/auth. Por lo tanto, da igual cuál usemos. Uno de los encabezados de respuesta de estas solicitudes llama la atención:
Para quienes no conocen ese nombre, Express es un framework de servidor web de Node.js muy utilizado. Como atacantes, esta es una información potencialmente valiosa. Ahora conocemos (o al menos sospechamos firmemente) el lenguaje, el entorno de ejecución y el framework de este servidor. Aunque esto no confirma que use Mongoose y MongoDB, sí respalda esa intuición.
Si seguimos esa hipótesis y suponemos que MongoDB se usa en el flujo de inicio de sesión, quizá podríamos probar una inyección de MongoDB con el endpoint api/auth. Esta es una inyección de consulta que encontré en book.hacktricks.xyz, un sitio increíblemente útil:
En la sintaxis de consulta de MongoDB, $ne es un operador que encuentra los casos en que un campo no es igual al valor indicado. En SQL, sería así:
Si suponemos que este endpoint consulta MongoDB para encontrar un usuario donde username = X y password = Y, esta inyección equivaldría a decir: «dame el usuario cuyo nombre de usuario no sea nulo y cuya contraseña no sea nula». En teoría, esta consulta encontraría cualquier usuario válido. Abramos una terminal y probémoslo con cURL:
¡Parece que no será tan fácil! Volvamos al navegador y, con la pestaña de red aún abierta, busquemos otros endpoints de API que podamos intentar explotar. En esta captura, recargué la página y luego filtré el tráfico de red para ver las solicitudes que incluyen api en la ruta.

Vemos que la imagen del ganso se carga mediante una llamada a /api/image/goose.png. Al igual que el endpoint /api/auth, esta respuesta también incluye el encabezado X-Powered-By: Express. Podemos suponer que se usa el mismo servidor para servir archivos estáticos. Veamos qué pasa si intentamos solicitar otro archivo, como package.json:
Esa respuesta es una gran señal de alerta. Parece que:
El servidor intenta abrir desde el disco el archivo que especificamos
Es probable que el código de la aplicación esté en
/app
Todavía no sabemos si este servidor usa una biblioteca o código personalizado para servir archivos estáticos. Pero podemos probar una vulnerabilidad común de los servidores de archivos estáticos: recorrido de directorios. En resumen, usaremos ../ para solicitar un archivo del directorio principal de la ubicación donde se guardan las imágenes. Observa que debemos codificar la barra como URL (%2f), porque, de lo contrario, nuestra solicitud se interpretaría como /api/package.json:
Por fin confirmamos nuestra sospecha inicial: este servidor usa la biblioteca mongoose de MongoDB. También vemos que el código principal del servidor está en server.js. Probemos a obtener ese archivo:
¡Lo logramos! Analicemos este servidor para descubrir cómo avanzar hasta la bandera.
Análisis del sistema de autenticación
Estas son las secciones de server.js que componen el sistema de autenticación:
Hay mucho que analizar, así que resumiré mis conclusiones principales:
MongoDB no se usa en el sistema de autenticación.
Este código ignora el
usernamey solo compara un hash de lapasswordcon una variableADMIN_HASHcodificada de forma fija.Esta contraseña se procesa con un hash usando bcrypt, que requiere muchísimos recursos de cómputo para descifrarla por fuerza bruta.
Después de autenticarte correctamente, el servidor genera un token aleatorio y lo agrega al objeto global
SESSIONS. El token se devuelve al cliente como un encabezadoAuthorization. Luego, el cliente puede enviar ese encabezado a los endpoints que usan el middlewarerequireAuthentication.
Análisis del endpoint de la bandera
Para obtener la bandera, tendremos que:
superar la comprobación de autenticación
proporcionar el valor correcto para
flagen el cuerpo de la solicitud
Al solicitar el archivo models/user.model.js mediante nuestro exploit de recorrido de directorios, vemos que Flag es un modelo de Mongoose y que esta llamada a Flag.find() consulta MongoDB para encontrar entradas donde name = <flag>.
El controlador /api/flags no aplica ningún saneamiento ni validación al cuerpo de la solicitud. Así que parece que podemos volver a intentar nuestra inyección de MongoDB anterior en lugar del nombre de la bandera. Pero primero, tendremos que superar el middleware requireAuthentication().
Unir todas las piezas
Como señalamos antes, es poco probable que podamos descifrar por fuerza bruta el ADMIN_HASH en un plazo razonable. ¿Podemos engañar al servidor para que crea que ya estamos autenticados?
Veamos la función que valida el token de sesión:
El punto débil de esta comprobación es que supone que el objeto SESSIONS solo contiene tokens de sesión. Pero la herencia prototípica de JavaScript hace que el objeto SESSIONS se inicialice con algunas propiedades ocultas y no enumerables. Podemos obtener una lista de esas propiedades ejecutando esta instrucción en un REPL de Node.js o en la pestaña Consola de las herramientas para desarrolladores del navegador:
Si tenemos razón, deberíamos poder usar cualquiera de estas propiedades como token de sesión y superar la función checkToken(). Al combinar esto con nuestra inyección de MongoDB, la solicitud final queda así:
La respuesta a esta solicitud contiene todo el contenido de la colección flag, ¡incluida la bandera real!
¡Moongoose atrapado!
Este desafío mostró varias vulnerabilidades. Usamos el recorrido de directorios para obtener el código del servidor. Aprovechamos la herencia prototípica de JavaScript para eludir el sistema de autenticación. Por último, usamos una inyección de consulta de MongoDB para eliminar cualquier conjetura del endpoint /api/flags. Esta aplicación se creó con vulnerabilidades intencionales, pero este tipo de vulnerabilidades también se presenta en el mundo real. Si quieres aprender más sobre los distintos tipos de vulnerabilidades, cómo se explotan y cómo protegerte de ellas, Snyk Learn es un excelente recurso práctico.
¿Quieres saber cómo encontramos todas las demás banderas? Consulta nuestra página de soluciones de Fetch the Flag para ver cómo lo hicimos.
Empieza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag viendo nuestro taller virtual introductorio a pedido.



