The Mission Briefing
My first "Very Easy" box. Bike. A Linux machine with a web application. Eleven guided questions between me and the flag. I figured this would be a quick warm-up.
I was half right about the "quick" part.
Reconnaissance
My scan came back with a minimal attack surface. Two ports — SSH on 22 and port 80 showing as "tcpwrapped." That was new to me.
I learned something useful here: "tcpwrapped" means the service accepted the TCP connection but didn't respond to nmap's probes. It's usually a web service that's being picky about what requests it answers. The fix was running a more targeted scan:
This time it identified Node.js running Express middleware. The HTTP headers even included an X-Powered-By: Express header — basically advertising the technology stack to anyone who asks.
The Discovery
I browsed to the website and found a simple page with an email input field. Wappalyzer filled in the details: Express framework, Node.js, Handlebars template engine, jQuery 2.2.4.
That word — Handlebars — caught my attention. Template engines process user input to generate dynamic HTML. And if user input goes directly into a template without sanitization... that's Server Side Template Injection (SSTI).
I tested the theory. Instead of typing an email address, I typed:
If the server returned "49," it would mean my input was being evaluated as template code. But instead I got something even better — a verbose error message:
Handlebars confirmed. The error message didn't just tell me the template engine — it exposed the file path on the server. That's a lesson in itself: error messages are information leaks. Always pay attention to them.
The Exploit
So I had SSTI in Handlebars. Time to get code execution. I found a payload on HackTricks designed for Handlebars SSTI, URL-encoded it, and sent it through the input field.
Blocked. The payload tried to use require() to import Node.js modules like child_process (which lets you run system commands), but the sandbox prevented it. require simply didn't exist in the template context.
The Sandbox Escape
This was the part that really taught me something. require() was blocked directly, but the JavaScript runtime still had the global object available. And through global, I could access process. And process had a property called mainModule. And mainModule had its own require function.
It's like finding a side door when the front door is locked. The sandbox blocked the obvious path, but it didn't block the chain of object references that led to the same destination:
I wrapped this in a Handlebars template payload, URL-encoded it, and sent it. The response came back buried in the page output:
root.
The web server was running as root. No privilege escalation needed. This is a classic misconfiguration — web services should almost never run as root, precisely because if someone gets code execution (like I just did), they immediately have full system access.
The Flag
One final payload — same sandbox escape, but with cat /root/flag.txt instead of whoami:
Box complete. RCE as root through Handlebars SSTI with a sandbox bypass via process.mainModule.require().
What I Learned
Bike was my introduction to SSTI and it packed a lot of concepts into one "Very Easy" box:
- Targeted port scans reveal more — Running
nmap -sC -sV -p 80identified Node.js where the full scan only showed "tcpwrapped." When a service is vague, scan it specifically - Error messages are information goldmines — The Handlebars parse error revealed the template engine, backend paths, and file locations. Never ignore verbose errors
- SSTI = Server Side Template Injection — If user input goes through a template engine unsanitized, you can inject template directives that execute code. Test with
{{7*7}}or similar - Sandboxes have side doors — When
require()is blocked, traverse throughglobal → process → mainModule → require. The prototype chain is your friend - Services running as root amplify everything — My SSTI gave me code execution, and because the server ran as root, that was instant game over. No privesc needed