26 oct 2025·4 min de lectura

Vulnerabilidad de redirección abierta en callbacks de autenticación: cómo arreglarla

Aprende cómo ocurre una vulnerabilidad de redirección abierta en callbacks de autenticación, cómo la explotan los atacantes y cómo arreglar las redirecciones con listas de permitidos estrictas y parseo seguro de URLs.

Vulnerabilidad de redirección abierta en callbacks de autenticación: cómo arreglarla

Por qué las redirecciones en flujos de login pueden convertirse en un agujero de seguridad

Una redirección es una instrucción que dice al navegador: "ve a esta otra página". En los flujos de inicio de sesión, las redirecciones hacen que todo parezca fluido: tu app envía a alguien a iniciar sesión y luego lo devuelve a lo que intentaba ver.

Ese paso de «devuélveme» suele llevarse en un parámetro como returnTo, next o redirect. Si tu app acepta cualquier URL ahí, tienes una vulnerabilidad de redirección abierta.

Esto aparece constantemente en prototipos porque las redirecciones son una forma fácil de lograr una demo más pulida. El código generado por IA o hecho a la carrera a menudo opta por "hacer que funcione" y salta las reglas aburridas de seguridad.

El riesgo es mayor que "el usuario aterriza en la página equivocada". Tu dominio se convierte en un punto de lanzamiento confiable para enviar a la gente a un sitio controlado por el atacante. El enlace parece legítimo porque empieza en tu dominio real y a menudo incluye una pantalla de login conocida.

Un flujo de phishing realista se ve así: alguien hace clic en un enlace de "Iniciar sesión" desde un correo o chat, llega a tu página de login real, inicia sesión y luego es redirigido a una pantalla falsa convincente que le pide "volver a iniciar sesión", introducir un código MFA o confirmar datos de pago. Muchos usuarios no notan nada porque todo parecía normal hasta el paso final.

Qué significan las redirecciones abiertas y los callbacks de auth

Una redirección abierta ocurre cuando tu app deja que una entrada no confiable decida a dónde se envía un usuario. El ejemplo clásico es una URL como https://yourapp.com/redirect?to=..., donde to puede ser cualquier sitio.

Un callback de autenticación es la página a la que tu proveedor de identidad devuelve al usuario después del login. Tras iniciar sesión con Google, GitHub u otro proveedor, este envía al usuario de vuelta a tu app en una URL de callback para que tu app termine el inicio de sesión y cree una sesión.

El problema empieza cuando combinas ambos.

Un patrón común:

  • Un usuario intenta visitar /billing.
  • Tu app lo envía a /login?next=/billing.
  • Tras el login, tu callback lee next (o returnUrl, redirect, continue) y envía al usuario allí.

Si el callback acepta next=https://evil.example, has construido una redirección abierta en la parte más confiable de tu producto.

El impacto no se limita al phishing. Las redirecciones dentro de flujos OAuth también pueden aumentar el radio de daño cuando equipos pasan valores sensibles por URLs en builds tempranos. Incluso cuando solo intercambias un code de OAuth, puedes filtrar datos a través del historial del navegador, logs, cabeceras Referer, o simplemente haciendo que el usuario aterrice en una página diseñada para engañarlo y que entregue acceso.

Patrones riesgosos comunes de redirección a buscar

La mayoría de los bugs de redirección en auth empiezan igual: un equipo quiere "devolver al usuario a donde estaba", así que pasan un parámetro de redirección y lo tratan como seguro.

Señales de alerta a buscar:

  • Cualquier parámetro de consulta que controle la navegación post-login (next, returnTo, redirect, url) usado directamente en una redirección HTTP o window.location.
  • Código que acepta URLs completas (https://example.com) en lugar de rutas internas (/dashboard).
  • Destinos de redirección provenientes de localStorage, cookies o cabeceras que se tratan como confiables.
  • Una "validación" que solo comprueba startsWith('/').
  • Múltiples pasos de decodificación/normalización que hacen confuso qué fue validado frente a qué se utiliza.

Dos casos límite que tropiezan a muchos equipos:

Protocol-relative URLs: valores como //evil.com parecen una ruta, pero los navegadores los tratan como "usar el esquema actual e ir a evil.com". Una simple comprobación startsWith('/') los dejará pasar.

URLs codificadas: los atacantes pueden ocultar el mismo truco en la codificación. %2F%2Fevil.com se convierte en //evil.com tras decodificar. Si validas antes de decodificar, o decodificas más de una vez en distintos sitios, puedes aprobar una cadena y redirigir a otra.

Cómo abusan realmente los atacantes de las redirecciones abiertas

A los atacantes les gustan las redirecciones abiertas porque pueden "tomar prestado" la confianza de tu dominio. La víctima ve tu sitio real en la barra de direcciones, inicia sesión y solo se le envía a algo malicioso al final.

Un ataque muy común:

  1. El atacante comparte un enlace a tu dominio real que incluye un parámetro de redirección, por ejemplo ?next=https://evil.example.

  2. Tu app muestra la página de login real.

  3. Tras iniciar sesión, tu app redirige al usuario al sitio del atacante.

  4. El sitio del atacante muestra un mensaje creíble de "sesión expirada" o "confirma tu cuenta" y captura credenciales o códigos MFA.

OAuth puede empeorar esto si tu endpoint de callback intercambia o maneja códigos/tokens y luego redirige inmediatamente según una entrada controlada por el usuario. Incluso si los datos son de corta duración, esa ventana puede ser suficiente.

Un ejemplo realista: el enlace de login que envía usuarios fuera

Map every redirect path
Encuentra dónde se confían parámetros de redirección en middleware, UI y rutas de callback.

Un prototipo a menudo añade un parámetro returnTo para que el login se vea pulido.

Una URL normal podría ser:

/login?returnTo=/billing

El bug aparece cuando returnTo se trata como "cualquier URL" en lugar de "una ruta segura dentro de nuestra app".

Ahora esto también funciona:

/login?returnTo=https://attacker.example/fake-dashboard

Nada se rompe. El usuario inicia sesión con éxito y luego aterriza en un sitio que parece tu producto pero no lo es. Desde la perspectiva del usuario, tu login funcionó, así que la siguiente pantalla se siente confiable.

La lección es simple: una función de "volver a donde estabas" debe aceptar solo destinos seguros y esperados. Si puede apuntar a una URL externa, es una puerta abierta.

El modelo más seguro: rutas relativas más listas de permitidos estrictas

El enfoque más seguro es intencionalmente aburrido: trata el destino post-login como una ruta interna, no como una URL completa.

Regla 1: acepta rutas relativas, no URLs completas

Acepta solo valores como /settings o /billing. Evita aceptar https://... y rechaza explícitamente valores protocol-relative como //....

Una línea base útil es: requiere que empiece con una sola / y rechaza todo lo que empiece con //.

Regla 2: valida contra una lista de permitidos estricta

Aunque solo aceptes rutas relativas, quizá quieras restringir aún más a dónde pueden aterrizar los usuarios tras auth. Una lista de permitidos evita destinos incómodos o riesgosos como bucles de /logout, rutas que activan acciones sensibles o páginas que solo deberían ver ciertos roles.

Mantenla pequeña. Permite un puñado de rutas conocidas y seguras (o algunos prefijos seguros) y por defecto envía todo lo demás a una página segura como /dashboard.

Normaliza y parsea antes de decidir

Normaliza la entrada una vez: quita espacios y decodifica la codificación porcentual una sola vez. Luego valida la ruta resultante. Evita decodificar más de una vez o validar sobre una representación distinta a la que realmente vas a redirigir.

Haz que los fallos sean aburridos

Si el valor falta o es inválido, ignóralo y redirige a un destino seguro conocido. Registra los rechazos para poder detectar sondeos y código cliente roto.

Paso a paso: arreglar el manejo de redirecciones en un prototipo

Review your OAuth callback
Una segunda opinión sobre callbacks OAuth, sesiones y la navegación post-login.

La lógica de redirección suele estar repartida en middleware, handlers de callback y código UI. La forma más rápida de hacerla segura es tratar cada destino de redirección como entrada no confiable y centralizar la validación.

  1. Haz inventario de cada fuente de redirección: parámetros de consulta (next, returnTo, redirect, callback, continue), cookies, localStorage y cualquier middleware de auth que "recuerde" a dónde iba el usuario.

  2. Elige tu regla: para la mayoría de apps, acepta solo rutas relativas. Si realmente necesitas redirecciones externas (raro), permite solo una lista corta de orígenes exactos que controles.

  3. Normaliza una vez: trim, decodifica una vez y rechaza caracteres de control.

  4. Valida estrictamente:

  • Requiere una única / inicial.
  • Rechaza //, cualquier esquema como http: o javascript:, y las barras invertidas (\\) incluyendo barras invertidas codificadas.
  • Rechaza traversal como .. y bytes nulos.
  • Si permites URLs completas, exige que el origen coincida exactamente con tu lista de permitidos.
  1. Redirige y registra: ante fallo, envía al usuario a un valor por defecto seguro y registra el valor rechazado.

Errores comunes que mantienen la vulnerabilidad viva

La mayoría de las correcciones fallidas parecen "validadas" pero siguen tratando las redirecciones como cadenas simples.

Trampas comunes:

  • Lista de permitidos por substring (por ejemplo, comprobando includes('mydomain.com')). Los atacantes pueden usar mydomain.com.evil.com u ocultar texto confiable en la ruta/query.
  • Validar solo en el cliente. Las comprobaciones del cliente ayudan al UX, pero el servidor debe ser la puerta final.
  • Validar un parámetro pero redirigir con otro debido a helpers del framework o precedencia de parámetros.
  • Normalizar de forma inconsistente, validar antes de decodificar o decodificar varias veces.

También vigilad el pensamiento de "lo configuramos antes, así que es confiable". Si un valor se guarda en localStorage, un campo oculto o una cookie, el atacante aún puede editarlo o saltarse la página que lo puso.

Comprobaciones rápidas que puedes hacer antes de publicar

Fix the prototype before launch
Reparamos flujos de autenticación rotos de apps generadas por IA y hacemos las redirecciones seguras para producción.

Puedes detectar la mayoría de problemas de redirección con unas pocas pruebas enfocadas. El objetivo es simple: ningún valor controlado por el usuario debería mandar al navegador a un dominio inesperado, y los valores desconocidos deben aterrizar en un sitio seguro.

Prueba el parámetro que usa tu app tras el login (next, redirect, returnTo, callbackUrl). Confirma que una ruta interna normal funciona y luego prueba entradas que suelen pasar las comprobaciones ingenuas:

  • https://example.com (debería rechazarse)
  • //evil.com y %2F%2Fevil.com (deberían rechazarse)
  • \\\\evil.com (algunos frameworks normalizan esto de maneras sorprendentes)
  • Una ruta interna desconocida como /definitely-not-real (debería volver al valor por defecto seguro)

Repite las mismas pruebas tanto en el código de enrutado del cliente como en los endpoints del servidor que finalizan sesiones o manejan callbacks OAuth. Los atacantes usarán el camino más débil.

Próximos pasos: llegar a una configuración de redirecciones limpia y segura

Los bugs de redirección abierta rara vez viven en un solo lugar. En prototipos aparecen donde la app intenta ser útil tras el login: guards de rutas, middleware que rebotan usuarios no autenticados, handlers de callback OAuth, enlaces de invitación y flujos de onboarding.

Un buen estado final es aburrido: cada redirección es o bien una ruta relativa conocida y segura, o bien (si realmente la necesitas) una URL absoluta cuyo origen coincida con una lista corta de permitidos que controlas. Todo lo demás se ignora y se reemplaza por un valor por defecto seguro.

Si estás trabajando con una codebase generada por IA y quieres una segunda opinión, FixMyMess (fixmymess.ai) se centra en diagnosticar y reparar este tipo de problemas de auth y redirección, junto con problemas relacionados como secretos expuestos y patrones inseguros que funcionan en demos pero fallan en producción.