BookinglyTech News
Ciberseguridad

Un callback de localhost olvidado en Cognito filtra todos los tokens OAuth

Dejar http://localhost:3000/callback en las URLs permitidas del app client convierte ese puerto en un punto de exfiltración: cualquiera que escuche ahí se lleva el código y la sesión.

3 min de lecturaDev.to0 vistas

Si tu app client de Cognito sigue teniendo http://localhost:3000/callback entre las URLs de callback permitidas, tienes un endpoint de exfiltración de tokens en producción. Da igual que lo añadiera alguien durante el primer sprint para probar el login: mientras siga ahí, un atacante puede montar un enlace de autorización que devuelva el código y los tokens a cualquier servidor que escuche en el puerto 3000 de la máquina de la víctima. En estaciones comprometidas, equipos de desarrollo compartidos o runners de CI con puertos expuestos, eso significa que cada token que emite la aplicación es interceptable.

El flujo es el de siempre. El usuario pulsa "Login con Google/Facebook/SSO", el navegador va al /authorize de Cognito, se autentica contra el IdP y Cognito redirige a la callback con el authorization code, que la app canjea por tokens. La vulnerabilidad vive justo en ese salto: Cognito solo comprueba que el redirect_uri esté en la lista de callbacks permitidas del app client. Con localhost:3000 dentro, la redirección es válida a ojos del proveedor. La página de login es la legítima y el usuario no nota nada. Si el atacante controla lo que escucha en ese puerto, recibe el code, lo canjea y se queda con la sesión.

Con el flujo implicit la cosa empeora: los tokens viajan directamente en el fragmento de la URL y no hace falta canje alguno.

Los patrones que se repiten

El localhost es el caso más habitual, pero no el único. Los puertos comodín (varios localhost en 3000, 3001, 8080) multiplican los listeners que el atacante puede aprovechar. Un wildcard de subdominio del tipo https://*.example.com/callback permite registrar attacker.example.com y recibir la redirección, por ejemplo tras un subdomain takeover de un bucket S3 retirado o un CNAME colgando en Route 53. Un callback http:// junto a otro https:// abre la puerta a un atacante en la red, tipo wifi de cafetería o router comprometido. Y dejar la callback de staging en el app client de producción manda tokens reales a un entorno con menos controles, menos logs y probablemente menos protección de datos.

El problema es que ningún escáner pregunta lo que importa. Revisan el app client pieza a pieza: si hay client secret, si los scopes están acotados, si existe una callback configurada. Verifican que hay una URL, no si esa URL permite redirigir tokens a un servidor que controla un tercero. Eso no es un booleano.

Cuando se combina con el resto

La misconfiguración sola ya es un fallo. Con otros ajustes del mismo app client se convierte en una cadena. Si EnableTokenRevocation está en false, el usuario no puede invalidar los tokens robados. Si AccessTokenValidity son 24 horas y RefreshTokenValidity 365 días, la ventana del atacante pasa de minutos a un año. Si el implicit grant sigue habilitado, no hay canje en servidor que registrar. Y si entre los scopes aparece aws.cognito.signin.user.admin, el token robado permite llamar a las APIs de gestión de usuarios de Cognito: leer atributos, cambiar el correo, cambiar la contraseña. Ahí ya no es robo de sesión, es toma de control de la cuenta y, vía el mapeo de roles del identity pool, potencialmente de recursos de AWS.

Herramientas como Stave hacen análisis estático buscando precisamente estas interacciones entre ajustes, que es donde un chequeo individual no llega.

La corrección es cosa de treinta segundos: borrar la entrada. Lo que cuesta es encontrarla, porque el fallo lleva abierto desde el primer sprint y nadie lo mira.