Un chico de 16 años entró a Microsoft poniendo 'admin' en un token y encontró 17 billones de registros

Pantalla de codigo mostrando un token JWT y datos de Microsoft
Un token sin firmá es todo lo que separaba a un adolescente de los datos internos de Microsoft

Tenía 16 áos, un portátil y una palabra: admin. Así de simple fue el error que le abrió la puerta a los datos internos de Microsoft: no robó contrasenas, no usó malware, no tocó un solo servidor de forma ilegal. Solo mandó un token JWT sin firma con un campo de usuario que nadie se molestó en validar.

El resultado: 17,333,335,124,315 filas — diecisiete billones — repartidas en 17 bases de datos de analíticas, 9,863 tablas únicas y 24 configuraciones distintas. Todo técnicamente alcanzable desde una única consulta SQL.

Su nombre es Faav, un investigador de bug bounty que autodescribe su Specialty como "Hacker & Developer". Y el primer párrafo de su propio reporte es una advertencia que vale oro: "Me lo hice por mí, no por el medio ambiente". Vamos por partes.

La puerta de entrada: una página que decía "VPN REQUIRED"

El servicio se llama Titan, una plataforma interna de analítica de Microsoft. Su interfaz web estaba detrás de una página de VPN obligatoria para empleados, o sea: fuera del alcance. Excepto por un detalle clásico.

El frontend no enlazaba a ningún lado, pero el backend sí existía en otro subdominio, resuelto a un host de Azure. Su archivo público de Swagger listaba cuatro rutas: /GetConfiguration, /GetOnboardedTables, /v2/Query y /v2/Insert.

Tres exigían autenticación de Azure AD. La cuarta, /v2/Query — la que aceptaba SQL crudo — también la indicaba en la documentación. Y no la exigió en la práctica.

El bug: el bouncer que nunca miró la foto

Aquí está el corazón de todo. Titan validaba el contenido del token: tenant, audience, app ID, usuario. Lo que nunca hizo fue verificar la firma.

La diferencia es brutal en criptografía. Un JWT normal tiene tres partes: header.payload.firma. Si cambias un byte del payload, la firma deja de cuadrar y el servidor debe rechazarte. Titan nunca hizo esa comparación.

La analogía que usó el propio Faav es perfecta: "un hotel donde cada puerta tiene un lector de tarjetas funcional, pero cualquier tarjeta abre cualquier habitación". Toda la lógica de control de acceso existía en la aplicación. Solo faltaba la pieza que la hacia significar algo.

Diez días de prueba y error, campo por campo

Aquí está lo que convierte la historia en algo más que un escáneo. Empezó con un token de su propio tenant de prueba y fue cambiando un campo a la vez, leyendo los errores que Titan le devolvía:

El payload cambiaba, la firma se quedaba exactamente igual. Titan aceptaba los claims nuevos. Cuando cambió alg por none y dejó la sección de firma como un punto final vacío, el sistema lo aceptó sin pestañear.

Ahí surgió el problema práctico: el campo upn (el "nombre de usuario principal") debe verse como un correo. Titan rechazaba todos los intentos. Su bot de IA probó alias de servicio, direcciones de empleado, placeholders... nada.

La solución llegó a la 1:40 de la máana de un sábado, cuando Faav dejó de tratar el campo por su nombre y pensó en qué haría un desarrollador en el backend. Cambió upn a "admin".

La respuesta fue el número 1. admin se resolvió al usuario local ID 1, con rol Admin, y el SQL se ejecutó. "A veces la respuesta sí es literalmente admin", escribió.

Lo que hay detrás de esos 17 billones

No todo era información sensible de clientes, pero tampoco era inocuo. Lo que el servicio exponía:

Registros de usuarios internos con nombres, correos y historial de inicio de sesión. No eran credenciales de Microsoft reales, sino hashes de placeholder del modelo local de Superset.

Directorio de empleados: puestos, departamentos y jerarquía de gestión del personal asociado a Titan. Suficiente para montar un ataque de ingeniería social dirigido. No lo probó.

Analíticas de búsquedas de Bing: registros con términos de búsqueda, identificadores de usuario (MUIDs) y ubicación a nivel de país o estado derivada de la IP inversa. Limitó sus pruebas a dos filas, una por consulta, solo para demostrar alcanzabilidad.

Y lo que mas le preocupó: como los MUIDs aparecían en más de un dataset, era posible correlacionar actividad de un usuario entre servicios distintos y construir un perfil completo. Nunca lo hizo.

Cuando calculó el total, sospechó de su propia aritmética. Lo verificó por dos vías distintas — system.tables.total_rows y active system.parts — contando réplicas por shard. El número no cambió. Eran las 2 de la máana, y se quedó mirándolo.

El detalle útil: la IA no lo habría encontrado

El hallazgo viene con una reflexión honestamente rara en este género. Faav admite que su bot de IA, Antares, hizo el 90% del trabajo pesado: enumerar subdominios, descascar los mensajes de error del JWT campo por campo, mapear la superficie de ataque y colarse por las cuatro capas de validación.

Lo que no pudo hacer fue darse cuenta de que upn no era un UPN. Los modelos probaron aliases válidos porque el campo parecía un correo, y el campo parecía un correo. La respuesta "User not found" no era un rechazo: era una pista de que la autenticación ya había pasado.

Y el token sin firma, técnicamente, ya estaba dentro. Solo faltaba adivinar el nombre de usuario. Diez días de persistencia automática más una intuición humana a las 2 de la máana.

Por qué importa más allá del titular

La lección técnica es simple y vale la pena repetir: verifica la firma antes que nada. Titan tenía toda la lógica de autorización del mundo escrita en la aplicación, y todo eso no valía absolutamente nada porque faltaba una verificación de una línea.

El hallazgo llegó al MSRC (Microsoft Security Response Center) por programa de divulgación coordinada. Microsoft confirmó que investigó y que endureció el servicio, y que valora la investigación segura dentro de los términos de su Bug Bounty Program.

Hay un asterisco que conviene no pasar por alto: Microsoft tuvo control editorial sobre la publicación. Recortó secciones y cifras, y reformuló cómo se describía el impacto antes de la publicación. El investigador lo dice en el segundo párrafo, sin rodeos. Cuando el Duéo de la infraestructura edita el relato del incidente, siempre hay contexto que no llega al lector.

Diecisiete billones de filas. Dos de la máana. Y una sola palabra: admin.

El takeaway para los que programan

Si escribes autenticación, hay tres cosas que este caso deja claras:

1. Validar el contenido de un JWT sin validar su firma es no validar nada. Es como leer el carnet de identidad de alguien sin revisar el hologram.

2. Nunca tomes el nombre de un campo como su semántica. upn significa "correo" y el backend lo usaba como "usuario local". Los nombres de campo mienten; el código no.

3. Un endpoint oculto no es un endpoint seguro. Estará tras VPN no protege si el API vive en otro subdominio con Swagger publicado.

17 billones de filas. Dos puntos de la máana. Y una palabra que todos asumiríamos obvia: admin.

Comparte esto si alguna vez deployaste un endpoint de analíticas internas y te sentiste tranquilo porque "solo los empleados pueden entrar".

Nota ética: los datos sensibles citados están redactados en el reporte original. No se realizó ninguna acción sobre datos de clientes.