Jonathan Elkabas y Tomer Nahum

Nota del editor

Este escenario forma parte de una serie de ejemplos que demuestran el uso de EntraGoat, nuestro entorno de simulación Entra ID. Puede leer una descripción general de EntraGoat y su valor aquí.


Graph Me the Crown (y los papeles)

El Escenario 2 de EntraGoat demuestra cómo la autenticación basada en certificados atada a un principal de servicio existente y a permisos de aplicación con privilegios excesivos puede llevar a comprometer al Administrador Global.

El atacante comienza con el acceso a un certificado filtrado que fue expuesto a través de artefactos de la tubería CI/CD. El certificado es válido para una entidad de seguridad de servicio que tiene el valor AppRoleAssignment.ReadWrite.All permiso de solicitud.

Al autenticarse en un contexto de sólo aplicación, el atacante abusa de este permiso para asignar otro, RoleManagement.ReadWrite.Directorya la misma entidad de seguridad. Esto permite a la entidad de seguridad autoasignar cualquier rol de directorio (incluido el de administrador global) a cualquier entidad de seguridad que desee. Por último, el atacante restablece la contraseña del administrador y recupera el indicador de escenario.

Este escenario pone de relieve los riesgos de la proliferación de certificados, los ámbitos de Graph con privilegios excesivos y la naturaleza de los tokens de aplicación exclusiva. También destaca la distinción entre la aplicación de permisos a través de reclamaciones de tokens y la evaluación de directorios en tiempo real, que explicaremos en este tutorial.


Visión general de la ruta de ataque

  1. Punto de apoyo inicial: El atacante obtiene un certificado codificado en base64 y su contraseña, supuestamente filtrados a través del reconocimiento de la tubería CI/CD.
  2. Identificación: El certificado coincide con un service principal existente denominado Corporate Finance Analytics.
  3. Abuso de permiso: El atacante se autentica utilizando el certificado y descubre que el servicio principal tiene AppRoleAssignment.ReadWrite.All permiso de solicitud.
  4. Escalada de privilegios: El atacante utiliza este permiso para asignar RoleManagement.ReadWrite.Directory al mismo servicio principal.
  5. Adquisición de roles: Con acceso a la gestión de roles de directorio, el atacante añade la entidad de seguridad de servicio como miembro del rol de directorio Administrador global.
  6. Compromiso de la cuenta: El atacante restablece la contraseña del usuario Global Admin y se autentica para recuperar la bandera.

Flujo de ataque

La figura 1 muestra el flujo de este ataque.

Figura 1. Flujo del escenario de ataque Me the Crown (and Roles)

¿Cómo sortea este ataque los controles normales de autenticación?

Este escenario abusa del modelo de aplicación de Microsoft Entra ID, donde las aplicaciones consisten en un registro global y un servicio principal local. Los permisos exclusivos de la aplicación se solicitan a en el nivel de registro de la aplicación y se conceden en el nivel del servicio principal. Además, los principales de servicio pueden autenticarse independientemente de los usuarios usando el flujo de credenciales de cliente OAuth 2.0.

Cuando se registra un certificado válido para un service principal (en su keyCredentials.customKeyIdentifier ), se puede utilizar para autenticar sin entrada interactiva o autenticación multifactor (MFA). Esta ruta de autenticación solo para aplicaciones opera fuera de las protecciones centradas en el usuario, lo que permite que un certificado comprometido acceda directamente a las API de Graph con los permisos o roles asignados al principal del servicio.

Esta ruta de ataque combina:

  • Higiene deficiente de los certificados (menos relevante para Entra ID)
  • Permisos excesivos y no supervisados de las aplicaciones
  • El diseño intencionado de AppRoleAssignment.ReadWrite.All para evitar* la experiencia de consentimiento del administrador en contextos de sólo aplicación

Encadenando estas condiciones, los atacantes pueden autenticarse como el principal de un servicio, escalar a ámbitos sensibles de Graph como RoleManagement.ReadWrite.Directory para otorgarse a sí mismos un rol privilegiado (hasta Administrador Global) sin tocar nunca una sesión de usuario después de haber obtenido las credenciales del principal del servicio.

NOTA: Se pueden encadenar rutas de ataque adicionales a ésta, ya que cualquier identidad con Application Administrator o Cloud Application Administrator o cualquier servicio principal con el Application.ReadWrite.All o propiedad sobre el principal del servicio de destino, como se demuestra en Escenario 1-también puede aprovechar esta vía de ataque añadiendo credenciales para acceder por la puerta trasera a la entidad de seguridad del servicio.

Por último, es importante distinguir entre los distintos usos de la autenticación basada en certificados (CBA). Cuando la configuración de CBA está deshabilitada a nivel de inquilino, esa restricción sólo se aplica a la autenticación de usuario interactiva, no al flujo de aserciones de cliente OAuth para aplicaciones. Los principales de servicio pueden seguir autenticándose mediante certificados si se registra una credencial válida en su KeyCredentials propiedad. Entra ID lo trata como un mecanismo de autenticación independiente. Este desacoplamiento arquitectónico puede llevar a falsas suposiciones sobre la aplicación de políticas de certificados en todo el inquilino.

*De hecho, este permiso no "evita" el consentimiento del administrador, sino que es el consentimiento del administrador. El permiso en sí requiere que se conceda inicialmente el consentimiento del administrador y, a continuación, permite al titular (y a los adversarios, por supuesto) conceder mediante programación el consentimiento a otras aplicaciones como parte de la interacción entre máquinas.


Cómo detectar y defenderse contra el uso indebido de la autenticación exclusiva de aplicaciones

La postura defensiva en Entra ID debe centrarse en restringir el uso de amplios permisos de aplicaciones y supervisar de cerca a los principales de servicio que desempeñan funciones sensibles como AppRoleAssignment.ReadWrite.All y RoleManagement.ReadWrite.Directory.


Los equipos de seguridad deben:

  • Auditoría periódica de los principales de servicio para funciones de aplicaciones con privilegios elevados.
  • Seguimiento de la creación de nuevas credenciales en los principales de servicio, especialmente cuando se realiza fuera de la automatización aprobada.
  • Detectar cambios en las asignaciones de funciones de las aplicaciones que podrían permitir una escalada de privilegios.

Las soluciones de Semperis proporcionan capas de defensa, empezando por indicadores de exposición (IOE) e indicadores de compromiso (IOC) que identifican configuraciones de riesgo y alertan sobre valores predeterminados peligrosos, como directores de servicio con permisos sensibles, políticas de autorización demasiado permisivas para configurar aplicaciones y falta de controles en torno a la seguridad de los directores de servicio.


Escenario en profundidad: Recorrido paso a paso de la solución

Echemos un vistazo a los pasos para simular la omisión de los controles de autenticación y comprender cómo permite el compromiso Global Admin.


Paso 1: Punto de apoyo inicial con un certificado comprometido

Comenzamos el Escenario 2 con un certificado comprometido(Figura 2), supuestamente volcado durante el reconocimiento de la tubería CI/CD. Está codificado en base64, protegido con contraseña y abandonado como la carga que cae de un camión de DevOps.

Figura 2. Un certificado comprometido, tirado en nuestro regazo

Para identificar al propietario del certificado, primero lo descodificamos en un objeto X509Certificate2 utilizable e inspeccionamos sus metadatos:

      $certBase64 = "[BASE64_CERTIFICATE_BLOG]"
$certContraseña = "¡AccesoCabra!123"

$certBytes = [System.Convert]::FromBase64String($certBase64)
$cert = Nuevo objeto System.Security.Cryptography.X509Certificates.X509Certificate2($certBytes, $certPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

$cert | Select-Object Asunto, Emisor, Huella digital, NotBefore, NotAfter | Formato-Lista

Podemos ver que el certificado es autofirmado y emitido para una aplicación llamada Corporate Finance Analytics (Figura 3).

Figura 3. Identificación de la aplicación para la que se ha emitido nuestro certificado

Alternativamente, podemos iterar a través de todos los registros de aplicaciones en el tenant y buscar una huella digital de certificado que coincida. Cada objeto de aplicación en Entra ID tiene un KeyCredentials que contiene metadatos sobre certificados o claves públicas asociados a la aplicación que se utilizan para la autenticación en contextos de sólo aplicación. Cada entrada incluye un CustomKeyIdentifierque almacena la huella digital del certificado (Figura 4) en formato binario y permite la búsqueda o correlación con certificados conocidos.

Figura 4. Huella digital del certificado

La siguiente función puede realizar la búsqueda:

      function Find-AppRegistrationByThumbprint {
    param([string]$Thumbprint)
    
    # Get all application registrations and check for matching certificate thumbprint
    $allApps = Get-MgApplication -All
    
    foreach ($app in $allApps) {
        if ($app.KeyCredentials) {
            foreach ($keyCred in $app.KeyCredentials) {
                # Compare thumbprints (certificate matching)
                if ($keyCred.CustomKeyIdentifier) {
                    $credThumbprint = [System.Convert]::ToHexString($keyCred.CustomKeyIdentifier)
                    if ($credThumbprint -eq $Thumbprint) {
                        Write-Host "Certificate match found for: $($app.DisplayName)" -ForegroundColor Cyan
                        return $app
                    }
                }
            }
        }
    }
    return $null
}

Microsoft Graph nunca almacena ni devuelve el contenido real del certificado ni las claves privadas. La consulta del KeyCredential mediante Graph API sólo revela los metadatos del certificado registrado, nunca el certificado en sí, por muy elevados que sean los privilegios de la identidad solicitante. Esto refuerza la necesidad crítica de un manejo y almacenamiento seguros de las claves privadas en toda la organización.

Para identificar el principal de servicio asociado, nos autenticamos como usuario con privilegios bajos utilizando Connect-MgGraph. Una vez que extraemos el AppId detrás de Corporate Finance Analytics (Gráfico 5), podemos autenticarnos directamente como el principal del servicio con el certificado.

Figura 5. Extracción del AppId

NOTA: Aunque la CBA esté desactivada en el inquilino(Figura 6), la autenticación en la entidad de seguridad del servicio mediante el certificado seguirá siendo correcta. Esto se debe a que la CBA basada en usuario se refiere a una experiencia de inicio de sesión interactiva basada en navegador, mientras que las entidades de seguridad dependen de un flujo de credenciales de cliente OAuth 2.0 no interactivo que utiliza aserciones de certificado. La desactivación de la CBA sólo afecta a la autenticación interactiva de usuarios con certificados y no tiene ningún impacto en la autenticación programática de service principals. Estos son caminos de autenticación separados en la plataforma Entra ID.

Figura 6. La CBA está desactivada en este inquilino Entra ID

Paso 2: Descubrir los permisos de la aplicación y construir nuestra ruta de ataque

Después de autenticarnos como el principal del servicio utilizando el certificado, comprobamos su roles en el JWT concedido a través del Get-MgContext y descubre que mantiene AppRoleAssignment.ReadWrite.All (Gráfico 7). Este permiso permite a la entidad de seguridad asignar CUALQUIER función de aplicación a todas las entidades de seguridad, incluida ella misma.

Figura 7. Comprobación de la declaración de funciones de la entidad de seguridad Comprobación de la declaración de funciones de la entidad de seguridad

Para escalar, enumeramos el principal del servicio Microsoft Graph (GraphAggregatorService), que contiene todos los roles OAuth asignables y está presente en cada tenant Entra como una aplicación first-party. Es instanciada desde un registro de aplicación global alojado en el propio tenant de Microsoft y puede ser identificada por el AppId estático de 00000003-0000-0000-c000-000000000000. Como todas las aplicaciones multi-tenant, aparece en cada tenant cliente como un servicio local principal-la identidad real utilizada para aplicar el control de acceso. (Ya hablamos del modelo de aplicación Entra ID en Escenario 1.)

El AppId en Figura 8 apunta a la definición global de Microsoft Graph, que contiene todos los roles OAuth asignables que definen ámbitos de permisos exclusivos de aplicaciones como Directory.Read.All o RoleManagement.ReadWrite.Directory. Cada tenant tiene una instancia principal de servicio local de la aplicación global, y Los permisos OAuth se asignan a nivel de la entidad de seguridad del servicio vía AppRoleAssignment. Esto permite que las aplicaciones funcionen independientemente de los usuarios, con los ámbitos definidos en el manifiesto global de la aplicación pero aplicados por el principal de servicio local del inquilino.

Dato curioso: ¡actualmente hay 576 permisos Graph únicos!

Figura 8. Enumeración de los permisos OAuth asignables de Graph

Ahora que hemos localizado el principal del servicio Microsoft Graph y tenemos sus permisos OAuth asignables, el siguiente paso es elegir con qué permiso escalar. El más impactante para nuestro propósito de ataque es RoleManagement.ReadWrite.Directorysegún una nota en el sitio oficial documentación (Figura 9):

Figura 9. Advertencia de Microsoft sobre los permisos que permiten conceder autorización

Este nivel de acceso es peligroso por diseño. Otorga control programático total sobre las APIs de gestión de roles de Entra ID y soporta la escalada directa de privilegios sin interacción del usuario.


Paso 3: Asignar permisos peligrosos

Podemos asignar este permiso a nuestro principal de servicio comprometido utilizando los siguientes comandos:

      $roleManagementRole = $graphSP.AppRoles | Where-Object { $_.Value -eq "RoleManagement.ReadWrite.Directory" }

$appRoleAssignmentParams = @{
    PrincipalId = $SP.Id
    ResourceId = $graphSP.Id
    AppRoleId = $roleManagementRole.Id
}

New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id -BodyParameter $appRoleAssignmentParams

Una vez ejecutado este comando, aunque hayamos concedido correctamente a la entidad de seguridad del servicio la contraseña RoleManagement.ReadWrite.Directory permiso, inspeccionado por Get-MgContext-no observamos ningún cambio en el contexto de seguridad actual (Figura 10). Se trata de un comportamiento esperado.

Figura 10. Observación de la ausencia de cambios en el contexto de seguridad de la entidad de seguridad principal

Los tokens de acceso emitidos por Entra ID son como instantáneas estáticas de las reclamaciones concedidas en el momento en que se emitió el token. Cuando Connect-MgGraph actúa como un cliente OAuth2.0 e inicia un flujo de autenticación contra el token endpoint (https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token). Este punto final valida las credenciales presentadas y emite un token de acceso JWT firmado para él con las reclamaciones de la persona que llama (como roles de aplicación, ámbitos y contexto de inquilino) en función del estado de autorización actual en el momento de la solicitud.

Dado que los tokens de acceso no se actualizan dinámicamente cuando cambian los permisos, los roles de aplicación recién concedidos (como el que acabamos de añadir) no aparecerán hasta que se obtenga explícitamente un nuevo token. Para obtener los privilegios actualizados, tenemos que terminar la sesión existente (Disconnect-MgGraph) y volver a autenticarse en él (Connect-MgGraph) para activar la emisión de un nuevo token de acceso que incluya las nuevas reclamaciones (Figura 11).

Figura 10. Observación de la ausencia de cambios en el contexto de seguridad de la entidad de seguridad principal

Con el RoleManagement.ReadWrite.Directory concedido, la entidad de seguridad de servicio que tenemos ahora puede modificar las asignaciones de funciones de directorio para cualquier identidad del inquilino, incluida la adición de sí misma a la función de administrador global:

      $globalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10" # GA role GUID
$globalAdminRole = Get-MgDirectoryRole -Filter "roleTemplateId eq '$globalAdminRoleId'" -ErrorAction SilentlyContinue

$roleMemberParams = @{
    "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$($SP.Id)"
}

New-MgDirectoryRoleMemberByRef -DirectoryRoleId $globalAdminRole.Id -BodyParameter $roleMemberParams

Ahora, si comprobamos los roles asignados que tiene el principal de servicio comprometido, podremos ver el GUID del Administrador Global(Figura 12).

Figura 12. Visualización del GUID del administrador global

Si lo ha seguido de cerca, puede destacar una diferencia de comportamiento clave:

¿Por qué es necesario obtener un nuevo JWT para la identidad después de conceder un permiso de aplicación, pero no después de asignar un rol de directorio?

La respuesta está en cómo Entra ID aplica estos dos modelos de autorización:

  • Permisos de aplicación (como RoleManagement.ReadWrite.Directory) se emiten como reclamaciones estáticas dentro del token de acceso JWT en el momento de la autenticación. Estos permisos se representan en el roles (o scp en flujos delegados). El token es, de hecho, una aserción firmada criptográficamente que refleja los roles y ámbitos de la aplicación de la persona que llama en el momento de la emisión y, por lo general, cualquier cambio en estos permisos requiere la reemisión del token.
  • Funciones del directorio (como Global Administrator) también se emiten como reivindicaciones estáticas dentro del token de acceso, pero siguen un modelo de aplicación diferente. Mientras que los tokens pueden incluir asignaciones de roles de directorio a través de la función wids reclamación o la group (en caso de pertenencia a un grupo), la mayoría de las API de Microsoft normalmente evaluar estos roles dinámicamente en tiempo de ejecución. Cuando se realiza una solicitud, el backend consulta las asignaciones de roles actuales en Entra ID para el ID de objeto del solicitante. Esta búsqueda en tiempo real permite que los nuevos roles asignados entren en vigor inmediatamente sin necesidad de renovar el token.

Dicho esto, Microsoft señala explícitamente en su documentación de referencia sobre reclamaciones de tokens de acceso que:

En roles, groups, scpy wids no son una lista exhaustiva de cómo un recurso puede autorizar a un usuario o aplicación, ni tampoco son una lista exhaustiva de los permisos concedidos a la persona que llama. El recurso de destino puede utilizar otro método para autorizar el acceso a sus recursos protegidos.

Paso 4: Pasar a una sesión de administrador

Con el rol GA asignado a nuestro principal de servicio, ahora poseemos privilegios completos a nivel de directorio. Esto nos permite restablecer la contraseña del usuario administrador de destino y asumir su identidad(Figura 13).

Figura 13. Asumiendo la identidad de nuestro usuario administrador objetivo

Para recapitular el paso final, no usaremos TAP o el portal Azure como hicimos en el Escenario 1.

En su lugar, aprovecharemos el1 Get-MSGraphTokenWithUsernamePassword para autenticarse en Microsoft Graph con las nuevas credenciales de administrador y recuperar el indicador de la función /me punto final (Figura 14), fieles al título de este escenario: Graph Me the Crown (and Roles).

Figura 14. La bandera es capturada

Una vez completado el escenario, ejecutamos el script de limpieza(Figura 15) para restaurar el inquilino a su estado original.

Figura 15. La limpieza de EntraGoat nos prepara para nuestro siguiente escenario

Encuentre sus puntos ciegos

Este escenario ilustra cómo los permisos Graph exclusivos para aplicaciones combinados con la autenticación basada en certificados pueden crear puntos ciegos en la gestión tradicional de identidades.

No es necesario comprometer al usuario. No se requiere ningún flujo interactivo. Encadenando dos permisos Graph-AppRoleAssignment.ReadWrite.All y RoleManagement.ReadWrite.Directory-el atacante escala silenciosamente un principal de servicio a Administrador Global.

Los equipos de seguridad deben tratar los permisos de aplicación y las credenciales principales de servicio como activos críticos, no como identidades secundarias. Imponer una gobernanza estricta sobre el uso de certificados y la asignación de permisos de Graph es esencial para prevenir la escalada de privilegios sin cabeza como la que se modela aquí en el Escenario 2.


Acepta tu próximo reto EntraGoat

Nota final

  1. https://github.com/BloodHoundAD/BARK

Descargo de responsabilidad

Este contenido se proporciona únicamente con fines educativos e informativos. Su objetivo es promover la concienciación y la corrección responsable de las vulnerabilidades de seguridad que puedan existir en los sistemas que usted posee o está autorizado a probar. El uso no autorizado de esta información con fines maliciosos, explotación o acceso ilegal está estrictamente prohibido. Semperis no respalda ni aprueba ninguna actividad ilegal y declina toda responsabilidad derivada del uso indebido del material. Además, Semperis no garantiza la exactitud o integridad del contenido y no asume ninguna responsabilidad por los daños derivados de su uso.