Cómo funciona el phishing mediante código de dispositivo de OAuth 2.0 en Azure y Google

Acompáñame en un recorrido por la vertiginosa y resonante locura de los ataques de identidad. Hoy te presento un caso de estudio sobre cómo distintas implementaciones de OAuth 2.0, el esquema de autenticación principal usado en la mayoría de los proveedores de identidad, pueden tener superficies de ataque radicalmente diferentes.

Los protagonistas del caso de estudio de hoy son dos empresas tecnológicas emergentes y ambiciosas directo de Silicon Valley: Microsoft y Google. Quizás hayas oído hablar de ellas.

Veamos cómo se comporta uno de mis ataques de identidad favoritos, el phishing mediante código de dispositivo, entre estos dos proveedores de identidad. Al final de este blog, tú, querido lector, podrás apreciar cómo se compara la implementación de OAuth 2.0 entre estos dos proveedores de identidad, y cómo eso facilita o anula directamente el radio de impacto de este ataque extremadamente poderoso.

¿Listo para ponerte nerd con esto? Pongámonos nerds con esto.

Conoce el flujo de código de dispositivo

Antes de entender el phishing mediante código de dispositivo, necesitamos entender la función que permite que este ataque exista en primer lugar. Comencemos con un desglose independiente de la implementación de la función en cuestión y cómo se explota. Es hora de conocer el flujo de código de dispositivo, también conocido como la concesión de autorización de dispositivo.

Figura 1: Resumen de la concesión de autorización de dispositivo de OAuth 2.0

Yo leí el RFC para que tú no tengas que hacerlo. Aunque te lo recomendaría si buscas algo para dormirte.

La concesión de autorización de dispositivo se usa en escenarios donde se quiere autenticar un dispositivo conectado a internet que carece de un mecanismo de entrada tradicional, como un teclado, un navegador web o similar. El RFC llama a estos dispositivos con entrada limitada. Imagina una impresora IoT común, un televisor inteligente, una tostadora con WiFi y pantalla táctil, o cualquier otro dispositivo conectado a internet que carezca de una interfaz de usuario completa.

Si quieres autenticar tu tostadora con WiFi en tu inquilino de la nube (y, de verdad, ¿quién no querría la emoción de hacer eso?), tienes un problema entre manos. No puedes navegar a la página de inicio de sesión de tu inquilino desde tu tostadora e ingresar tu nombre de usuario y contraseña. Pero imaginemos que en este escenario tu tostadora tiene una pantalla táctil donde podrías ingresar un código de seis dígitos. No puedes navegar a la página de inicio de sesión ni escribir tu nombre de usuario y contraseña, entonces, ¿cómo podrías autenticar la tostadora?

El flujo de código de dispositivo te permite:

  • Iniciar el proceso de autenticación desde la tostadora solicitando un código de dispositivo.
  • Pasar a una computadora con teclado y navegador web.
  • Usar el navegador web para ingresar el código de dispositivo junto con tus credenciales y completar el proceso de autenticación...
  • ...y luego volver a la tostadora y recuperar el token de acceso que generaste al iniciar sesión.
__wf_reserved_inherit
Figura 2: Diagrama de una autenticación hipotética con código de dispositivo

Esto resuelve de forma efectiva el problema de autenticar dispositivos con entrada limitada. ¡Genial! Ahora puedes unir tu tostadora a tu inquilino.

Si esto suena extremadamente específico, es porque lo es. Esta función de OAuth tiene una aplicación muy acotada y solo la usan ciertos tipos de usuarios en un puñado de escenarios. Dicho de forma simple, es poco probable que el usuario promedio necesite saber sobre el flujo de código de dispositivo.

Si hay algo en lo que los hackers son buenos, es en encontrar funciones específicas como el flujo de código de dispositivo y convertir este método de autenticación puntual en un vector de acceso inicial. Así que hablemos del phishing mediante código de dispositivo.

Phishing mediante código de dispositivo en Microsoft 365

El phishing que explota el flujo de código de dispositivo no es nada nuevo. Algunos de los grandes investigadores de identidad de nuestra época ya han escrito sobre el tema, incluidos Dr. Nestori Syynimaa, Dirk-jan Mollema, y Lina Lau (@inversecos). Aplicando cierta destreza de ingeniería social, el flujo de código de dispositivo puede ser aprovechado de forma maliciosa para generar un token de acceso a algún recurso, que luego el atacante puede recuperar.

Así es como sucede:

  • El atacante solicita un código de dispositivo desde la API de código de dispositivo del proveedor de identidad. Solicitar el código no requiere autenticación y usa una API completamente legítima para hacerlo.
  • El atacante redacta un correo electrónico convincente y se lo envía a la víctima, lo que la induce a ir a la página de inicio de sesión de código de dispositivo e ingresar el código.
  • La víctima accede y va a la página de inicio de sesión de código de dispositivo, completamente legítima, donde ingresa el código de dispositivo, su nombre de usuario y contraseña, y su código de MFA. Y entonces… no pasa nada, al menos desde la perspectiva de la víctima… 
  • …pero el atacante ha estado monitoreando la API de código de dispositivo todo este tiempo y pregunta cada 10 segundos: “¿Ya se autenticó?”. Una vez que el usuario completa la autenticación, la API de autenticación de código de dispositivo genera un conjunto de tokens de acceso para la víctima.
  • El atacante solicita ese conjunto de tokens, que convierte en acceso inicial.
__wf_reserved_inherit
Figura 3: Diagrama del ciclo de ataque

Veamos un ejemplo concreto donde se usa el flujo de código de dispositivo de Azure para realizar este ataque.

Primero, el atacante solicita el código de dispositivo. Esto es tan simple como hacer un cURL a la API de flujo de código de dispositivo de Azure. Recuerda: como atacante, simplemente puedo hacer esto. No necesito autenticarme ni demostrar quién soy. Mientras estructure la solicitud correctamente, la API entregará un código de dispositivo para iniciar el proceso. La solicitud cURL se vería más o menos así…

Loading Gist...

…donde:

  • La URL solicitada es el endpoint común de la API de autenticación de código de dispositivo de Azure.
  • El parámetro client_id es el GUID del cliente que hace la solicitud (presta atención a esto; será importante más adelante).
  • El parámetro resource es el recurso solicitado. En este caso, le estamos pidiendo a la API que genere un conjunto de tokens que puedan autenticar un dispositivo con la Graph API.

Si la solicitud cURL está formateada correctamente, la API responde con un blob JSON que contiene el código de dispositivo, el código de usuario y algunas instrucciones útiles sobre cómo usarlos:

Figura 4: Comando curl seguido de la respuesta de la API

En este punto, en un escenario legítimo de autenticación por código de dispositivo, el usuario pasaría de este dispositivo con entrada limitada a una computadora con navegador web, iría a https://microsoft.com/devicelogin e iniciaría sesión usando su navegador. Pero en el escenario de ataque, el atacante crea un phishing convincente que le indica al usuario ir a la misma página de inicio de sesión por código de dispositivo, iniciar sesión y esperar más instrucciones.

Supongamos que el atacante envió este phishing con un pretexto convincente, así que la víctima va a https://microsoft.com/devicelogin e ingresa el código proporcionado, ignorando debidamente la advertencia sobre ingresar códigos de fuentes no confiables…

Figura 5: Solicitud de código

Luego, la página le pide a la víctima un nombre de usuario y contraseña…

Figura 6: Solicitud de inicio de sesión

… y pasa a la verificación de MFA, que la víctima completa…

Figura 7: Solicitud de MFA

Luego, la página muestra una última pregunta para confirmar si la víctima intenta iniciar sesión en Microsoft Office, que el usuario asume que es rutinario y hace clic en continuar…

Figura 8: Solicitud que pregunta la intención de iniciar sesión en Microsoft Office

Y finalmente, la víctima ve esto (Figura 9):

Figura 9: Pantalla de confirmación que indica que el usuario ahora tiene la sesión iniciada en Microsoft Office en un dispositivo

Lo cual, por supuesto, resulta extremadamente confuso si eres quien cayó víctima de este ataque.

Mientras tanto, en otro lugar, el atacante construye otro comando cURL para consultar la API de autenticación de Azure:

Loading Gist...

…donde:

  • Los parámetros client_id y resource son los mismos que en la solicitud inicial.
  • El grant_type especifica que el método de autenticación usado para este token fue el flujo de código de dispositivo.
  • El parámetro code es el código de dispositivo devuelto por la solicitud inicial.
  • La URL aquí es el endpoint de token OAuth de Azure, que gestiona la solicitud y respuesta para generar tokens de autenticación.

Una vez que el usuario cae víctima del phishing, su autenticación genera un conjunto de tokens que ahora residen en el endpoint de la API de token OAuth y se pueden recuperar proporcionando el código de dispositivo correcto. El atacante, por supuesto, conoce el código de dispositivo porque fue generado por la solicitud cURL inicial a la API de inicio de sesión por código de dispositivo. Y aunque ese código es inútil por sí solo, una vez que se engaña a la víctima para que se autentique, los tokens resultantes ahora pertenecen a cualquiera que sepa qué código de dispositivo se usó en la solicitud original.

Figura 10: La salida de la API de autenticación tras un phishing exitoso de código de dispositivo

Un ataque exitoso resulta en que el atacante obtenga un token de acceso y un token de actualización que son válidos para la víctima engañada. Los tokens son válidos para un recurso solicitado (en este caso, la API de Graph) y están limitados a un conjunto de permisos de OAuth que permiten al atacante acceder a recursos como si fuera la víctima engañada. ¡Ataque completo, acceso inicial logrado, cibercriminal feliz!

Hay algunas cosas que hacen que este ataque sea insidioso.

Primero, este ataque usa el flujo de autenticación legítimo en lugar de explotar alguna vulnerabilidad o error. Todo el contexto de phishing se mantiene en infraestructura legítima de Microsoft, usando API legítimas de Microsoft, para realizar el flujo de autenticación por código de dispositivo legítimo (aunque secuestrado). No hay explotación real durante este ataque, más allá de engañar a alguien para que use el flujo de código de dispositivo cuando no debería.

Segundo, debido al punto anterior sobre el uso de funcionalidad de autenticación legítima, el atacante puede usar URL legítimas durante la parte de phishing. Un usuario que esté al día con su security awareness training podría saber que debe sospechar de enlaces, pero podría bajar la guardia al ver https://microsoft.com/devicelogin como la URL. No estoy neutralizando esta URL en el texto de este blog por una razón: es 100 % totalmente legítima, benigna y segura de visitar. A lo largo de todo el ejemplo documentado arriba, nunca tuve que enviar a la víctima a algún sitio sospechoso ni hacer que ingresara sus credenciales ahí. Así que imagina lo poderoso que es cuando tú, como atacante, puedes usar una URL legítima de Microsoft para tu ataque de phishing.

Finalmente (y lo más importante), el atacante controla el tipo específico de recurso y token que se genera debido a este ataque. En el ejemplo anterior, el atacante crea un código de dispositivo limitado a la API de Graph y especifica un ID de cliente. ¿Recuerdas que te dije que recordaras el ID de cliente porque sería importante más adelante? Pues…

...el ID de cliente en el ejemplo anterior no es una cadena de caracteres aleatoria. De hecho, la implementación de OAuth de Microsoft incluye un conjunto no documentado de ID de cliente que están disponibles libremente para que cualquiera los use durante los flujos de autenticación. Esto se llama Family of Client IDs y fue descubierto recientemente por investigadores de Secureworks. Este tema es bastante complicado, pero para resumirlo brevemente: la combinación de un ID de cliente y el recurso solicitado genera un token que puede tener un alcance ajustado a distintas cosas.

Por ejemplo, el ID de cliente usado en el ejemplo anterior corresponde al cliente de Microsoft Office. Cuando el token se genera para la Graph API, el token resultante se puede usar para llamar a la Graph API y leer los correos del usuario, ver su calendario, leer sus archivos y muchas otras cosas según se especifique en el alcance del token:

Loading Gist...

Si bien la Family of Client IDs es fascinante, está en gran medida fuera del alcance de esta publicación de blog. Pero las dos conclusiones clave son:

  • El atacante especifica el recurso solicitado y el ID de cliente en la solicitud original, lo que afectará el poder del token generado.
  • Microsoft tiene un conjunto de ID de cliente disponibles públicamente y universalmente reconocidos que afectan el alcance del token resultante.

Para ilustrar brevemente el poder de dejar que el atacante controle el recurso y el ID de cliente durante la autenticación, basta con mirar el blog de Dirk-jan "Phishing for Primary Refresh Tokens and Windows Hello keys," en el que demuestra cómo un atacante puede aprovechar el phishing mediante código de dispositivo para generar un Primary Refresh Token y robarlo para obtener acceso inicial. Cabe destacar que esto es posible porque el atacante puede establecer el recurso de la solicitud de código de dispositivo como https://enrollment.manage.microsoft.com/ y el ID de cliente como el Microsoft Authentication Broker (29d9ed98-a469-4536-ade2-f981bc1d605e). El token generado al hacer phishing exitosamente a una víctima con estos parámetros es el tipo de token más poderoso en el esquema de tokens de Azure OAuth. Este token se puede usar, entre otras cosas, para iniciar sesión en recursos web, unir dispositivos fraudulentos a un inquilino, crear inscripciones de autenticación multifactor, y mucho, mucho más. Es el token más poderoso por mucho margen, ¡y está a solo un pequeño phishing de código de dispositivo de quedar bajo el control del atacante!

(Como nota al margen, si te interesa la investigación de identidad y el red team, lee todas y cada una de las publicaciones de blog de Dirk-jan. No te vas a decepcionar.)

¿Tiene que ser así?

Ya establecimos cómo se lleva a cabo el phishing mediante código de dispositivo en Azure y que es extremadamente poderoso. El atacante obtiene control sobre el tipo específico de recurso y el alcance de los tokens resultantes. Es un ataque que vive enteramente dentro del ecosistema de Microsoft. En el peor de los casos, se puede usar para robar el tipo de token más poderoso que existe.

Pero recuerda que el flujo de autenticación de código de dispositivo no es un mecanismo exclusivo de Azure. Más bien, Azure implementa el flujo de código de dispositivo como un método de autenticación. La especificación del flujo de código de dispositivo proviene del RFC de OAuth 2.0, y entender cómo los distintos proveedores de identidad lo implementan es fundamental para evaluar el riesgo de phishing mediante código de dispositivo. La especificación del flujo de código de dispositivo proviene del RFC de OAuth 2.0. Azure está lejos de ser el único proveedor de identidad que implementa el flujo de código de dispositivo. Así que no pude evitar preguntarme…

¿Significa esto que, sin importar qué proveedor de identidad uses, el phishing mediante código de dispositivo es igual de peligroso? ¿Acaso importa siquiera la implementación del código de dispositivo de tu Azure 2.0?

¡Alerta de spoiler! La respuesta es, sorprendentemente, NO. Y no hace falta ir más allá del buen viejo tío Google para ver un ejemplo donde los detalles de implementación tienen un efecto enorme en cuánto daño puede causar esta técnica.

Google: anulado de fábrica

Supongamos que estamos en un ejercicio de red team contra un cliente que usa Google como su proveedor de identidad principal. Intentemos recrear el ataque de phishing mediante código de dispositivo usando las API de Google Workspace/Google Cloud. Como siempre, un buen punto de partida es la documentación para desarrolladores. Y de inmediato queda claro que vamos a enfrentar una batalla cuesta arriba:

Figura 11: La documentación para desarrolladores de Google Cloud sobre el flujo de código de dispositivo

La documentación indica claramente que no todos los alcances son compatibles con el flujo de código de dispositivo. Pero los hackers nunca pierden la esperanza, así que examinemos qué alcances sí son compatibles:

Figura 12: La pequeñísima cantidad de alcances de permisos de OAuth 2.0 compatibles con la implementación de Google del flujo de código de dispositivo

Si conoces los alcances de OAuth 2.0 y su típica posibilidad de abuso por parte de atacantes, es posible que te acabes de caer de la silla. Si no los conoces, puedo resumirlo así: los primeros tres alcances son universales en todas las implementaciones de OAuth 2.0 y OpenID Connect (OIDC) y permiten una explotación limitada, si acaso alguna. Los demás alcances compatibles son extremadamente restringidos cuando buscamos primitivas de explotación.

O sea, seamos sinceros: en Azure puedes montar un ataque de phishing mediante código de dispositivo donde los tokens resultantes te permiten básicamente convertirte en el usuario víctima, unir un dispositivo malicioso al inquilino de la víctima, iniciar sesión en su correo y otros recursos, y otros tipos de acceso increíblemente poderosos. Esto hace que la implementación del flujo de código de dispositivo de Azure sea significativamente más peligrosa para el phishing mediante código de dispositivo que el enfoque restringido de Google. Y en Google, podemos usar el alcance de permisos de archivos de GDrive para *revisa notas* “ver, editar, crear y eliminar solo los archivos específicos de Google Drive que uses con esta app.”

Antes de siquiera haber empezado el ataque, la implementación de Google del flujo de código de dispositivo ya ha neutralizado en gran medida el potencial del ataque. Si algún miembro de un red team que lea esto sabe cómo convertir el alcance youtube.readonly en una toma de control de una cuenta, soy todo oídos (no bromeo, escríbeme: matt.kiely[at]huntresslabs.com, me encantaría conversar). Pero no me voy a quedar esperando.

Pero la esperanza es lo último que se pierde, así que intentemos llevar a cabo el ataque de todos modos. Hasta donde puedo ver, no existen IDs de cliente públicos para Google como sí los hay para Azure. Entonces, si queremos ejecutar el ataque, necesitamos un ID de cliente. La forma recomendada de obtener un ID de cliente es crear una aplicación de Google Workspace y configurarla para usar OAuth 2.0. Así que, si esperabas algo de anonimato al llevar a cabo el ataque, puedes olvidarte de eso.

Incluso si te tomas la molestia de construir una app de OAuth dentro de Google, siguen existiendo otras verificaciones y protecciones. Por ejemplo, si tu aplicación requiere demasiados permisos con mucho poder, debe publicarse y verificarse antes de que cualquiera pueda usarla:

Figura 13: El mensaje de error al intentar instalar una aplicación de Google OAuth 2.0 no verificada

Pero eso no será un problema para nosotros, ¿recuerdas? Estamos limitados a cuatro ámbitos de permisos: dos para GDrive y dos para YouTube. No estamos precisamente trabajando con primitivas de explotación poderosas aquí.

Los pasos técnicos para solicitar un código de dispositivo a Google son en gran parte los mismos que con Azure. Para variar, usaremos Python para hacer la solicitud inicial y consultar el endpoint hasta que se complete la autenticación:

Loading Gist...

Cuando ejecutamos el script, nos da un código de dispositivo. Supuestamente hacemos phishing a la víctima y le indicamos que se autentique, igual que con el ataque de phishing mediante código de dispositivo de Azure:

Figura 14: El resultado de ejecutar el script de phishing mediante código de dispositivo de Google Cloud

Una vez que la víctima ingresa sus credenciales, ¡obtenemos un token de acceso y un token de actualización! Pero buena suerte usando cualquiera de los dos para lograr el acceso inicial, dado el ámbito limitado.

Figura 15: Los tokens resultantes de una autenticación exitosa mediante código de dispositivo de Google Cloud

Nota importante: en realidad solo he tocado la superficie de la superficie de ataque con Google en comparación con Azure. Puede haber IDs de cliente sin documentar, ámbitos sin documentar disponibles para explotación, y muchos otros posibles vectores de ataque aquí que son desconocidos para mí y/o la comunidad. Pero creo que ya es evidente que los vectores de ataque en Google Cloud son significativamente más limitados en comparación con sus equivalentes en Azure.

Seguridad del flujo de código de dispositivo: análisis y conclusión

¡Qué diferencia entre los dos proveedores de identidad!

Si tuviera que resumir la conclusión clave aquí, sería esta: Google entiende que el flujo de código de dispositivo es un caso especializado y que solo se usa en ciertos escenarios, y limita los ámbitos disponibles para el flujo de código de dispositivo en consecuencia. Microsoft no impone tales limitaciones.

Puedes imaginar por qué Google eligió implementarlo así. Los dos conjuntos de permisos tienen que ver con GDrive y YouTube. Google entiende que los tipos de dispositivos cliente que probablemente usarían el flujo de autenticación por código de dispositivo son los televisores inteligentes (¡la documentación incluso lo especifica en el título! “OAuth 2.0 for TV and Limited-Input Device Applications”). No es difícil imaginar que los tipos de accesos que necesitaría un televisor se centran en el entretenimiento: “Oye, televisor inteligente, ¡reproduce mi video favorito de YouTube!”

Pero tómate un momento para apreciar el hecho de que dos implementaciones de la misma función de OAuth tienen superficies de ataque radicalmente diferentes asociadas a cada una. La falta de restricción de Azure sobre los ámbitos y recursos solicitados ya ha llevado a investigadores a identificar primitivas de ataque extremadamente poderosas para los ataques de phishing mediante código de dispositivo. Pero la implementación de Google impone restricciones severas al mismo tipo de ataque.

Si bien no está claro si otros ataques de identidad (ataques de concesión de consentimiento, incorporación fraudulenta de dispositivos, etc.) son tan efectivos en Google como en Azure, podemos concluir con seguridad que el phishing mediante código de dispositivo es mucho menos efectivo en Google Cloud que en Azure.

Misma función. Mismo diseño. Implementaciones diferentes. Superficies de ataque radicalmente diferentes! Esto demuestra que, aunque todas las implementaciones de OAuth 2.0 son iguales, resulta que algunas son un poco más iguales que otras.

También te podría interesar