Reconocimiento inicial
Como siempre comenzamos con un scan para detectar los puertos abiertos en el host victima:
nmap -sS --min-rate 1500 -p- --open -n -Pn 10.10.10.85
Como se aprecia solo se encuentra el puerto 3000 abierto. Realzamos otro scan para obtener los servicios y versiones que se están utilizando en ese puerto:
nmap -sCV -p3000 10.10.10.85
Y observamos que corresponde a un servicio web en el cual se esta utilizando Node.js Express framework
. Con esto presente ingresamos a la web para visualizar el contenido de esta.
Reconocimiento web
Vemos que al ingresar se muestra el mensaje Hey Dummy 2+2 is 22
:
Luego de enumerar un poco notamos que la web no tiene nada más que esto. Observando las cookies de sesión encontramos un valor parecido a un JWT
pero en realidad es un valor en base64 ya que no contiene los puntos de la firma de un JWT
normal:
Al decodificar el valor en base64 notamos que es un objeto que esta serializado:
Si tomamos estos valores y los modificamos como por ejemplo el valor de username
y lo enviamos en la cookie de sesión podemos ver que efectivamente cambian:
Node JS Deserialization Attack
Buscando damos con el siguiente post:
Exploiting Node.js deserialization bug for Remote Code Execution
El cual explica un método en el cual es posible realizar una ejecución remota de comandos mediante la explotación de deserialización insegura en node.js. En el cual se nos explica que:
Los datos no confiables pasados a la función
unserialize()
en el módulonode-serialize
pueden ser explotados para lograr la ejecución arbitraria de código pasando un objeto JavaScript serializado con una expresión de función invocada inmediatamente (IIFE
) Immediately invoked function expression.
→ Construcción del Payload
Para construir el payload es necesario contar con la librería de node.js node-serialize
. Está la podemos instalar con npm
:
Al ejecutarlo observamos que nos crea el objeto:
Como menciona el post, Ahora tenemos una cadena serializada que puede ser deserializada con la función unserialize()
. Pero el problema es que la ejecución del código no ocurrirá hasta que disparemos la función correspondiente a la propiedad rce del objeto.
Más tarde descubrí que podemos usar la expresión de función invocada inmediatamente (IIFE) de JavaScript para llamar a la función. Si usamos el doble paréntesis de IIFE
()
después del cuerpo de la función, la función será invocada cuando el objeto sea creado. Funciona de forma similar a un constructor de clase en C++.
Ahora se llama a la función serialize()
con el código del objeto modificado:
La vulnerabilidad en la aplicación web es que lee la cookie de la petición HTTP, realiza la decodificación base64 del valor de la cookie y lo pasa a la función unserialize()
. Como la cookie es una entrada no confiable, un atacante puede crear un valor de cookie malicioso para explotar esta vulnerabilidad.
Shell as Sun
Para agilizar la explotación podemos utilizar la herramienta nodejsshell.py para obtener una reverse shell:
Copiamos el payload entregado y lo reemplazamos en el primer script de la siguiente forma:
Desde ya nos ponemos en escucha:
Y codificamos el payload en base64 para enviarlo en la cookie:
Observamos que ya tenemos acceso:
Privesc
Al recibir la conexión notamos que estamos en el directorio del usuario sun. Dentro de este notamos el fichero output.txt
:
El cual si observamos podemos notar el mensaje de: Script is running...
Pero no sabemos que script. Además notamos que el output es generado por el usuario root
:
Detectando procesos con Pspy
En base al fichero detectado podemos pensar que se esta ejecutando un script que no podemos detectar. Para esto utilizaremos la herramienta pspy la cual debemos transferir a la maquina victima:
Le damos permisos de ejecución y la ejecutamos. Y luego de esperar por un rato podemos observar que el UID 0 correspondiente al usuario root, está ejecutando el siguiente comando:
Vemos que root ejecuta el script script.py
en el directorio Documents
del usuario sun
para luego depositar el fichero output.txt
. Al observar el contenido del script podemos ver que simplemente ejecuta un print de “Script is running…
” por lo que realmente no hace nada. Pero lo curioso de esto es que el propietario del script es el usuario que ya tenemos comprometido:
En base a esto podemos modificar el script para que se cambie el permiso de la /bin/bash
a SUID
:
Ahora debemos esperar a que el usuario root ejecute el script para poder cambiar el permiso de la /bin/bash
y escalar privilegios al usuario root:
Shell as root
Ya con permiso SUID en la bash podemos utilizar el parámetro -p para ejecutarla con permisos de root:
Con esto ya podemos leer la flag del usuario root.
Pwned! 🏴☠️