Todo tenant termina teniendo que responder la pregunta de los permisos Graph API delegados versus aplicación Entra mínimo privilegio: ¿cuál de los dos modelos de consentimiento es realmente más seguro, y cómo se le otorga a una aplicación el mínimo que necesita sin romperla? La mayoría de los equipos responden por instinto — los permisos delegados «se ejecutan como un usuario», así que parecen acotados; los permisos de aplicación «se ejecutan como la aplicación», así que parecen peligrosos. Ese instinto tiene razón a medias, y la mitad en la que se equivoca es la mitad que nadie audita.
El permiso que discretamente le da a una aplicación un punto de apoyo en cada buzón del tenant no es necesariamente un permiso de aplicación. Puede ser un permiso delegado que un administrador consintió en nombre de toda la organización — y ese otorgamiento vive en una colección de objetos distinta de la de los app roles, así que una revisión de permisos de aplicación nunca lo detecta.
Permisos Graph API Delegados Versus Aplicación Entra Mínimo Privilegio, Definición
La plataforma de identidad de Microsoft documenta dos escenarios de acceso, y la distinción es sobre quién actúa la aplicación, no sobre cuánto daño puede causar.
El acceso delegado significa que un usuario ha iniciado sesión y la aplicación cliente accede al recurso en su nombre. Microsoft es explícito sobre el techo: «The application isn't able to access anything the signed in user couldn't access.» Una aplicación con el permiso delegado Files.Read.All «is only able to read files that the user can personally access.» Los permisos delegados también se llaman scopes, y el objeto de otorgamiento resultante es un oAuth2PermissionGrant.
El acceso de aplicación (app-only) significa que la aplicación actúa por sí sola sin ningún usuario conectado — daemons, backups, automatización. Aquí el techo es el propio recurso: una aplicación con el permiso de aplicación de Microsoft Graph Files.Read.All «is able to read any file in the tenant using Microsoft Graph.» Los permisos de aplicación son app roles, el objeto de otorgamiento es un appRoleAssignment, y según la propia tabla comparativa de Microsoft, solo un admin puede consentirlos.
| Permisos delegados | Permisos de aplicación | |
|---|---|---|
| Contexto de acceso | En nombre de un usuario conectado | Sin usuario |
| Quién puede consentir | Los usuarios para sus propios datos; los admins para todos los usuarios | Solo un admin |
| Métodos de consentimiento | Estático (app registration) o dinámico (solicitado al iniciar sesión) | Solo estático |
| Otros nombres | Scopes, OAuth2 permission scopes | App roles, permisos app-only |
| Objeto de otorgamiento | oAuth2PermissionGrant | appRoleAssignment |
Esa última fila resume todo el problema. Los dos tipos de permisos viven en dos colecciones de objetos distintas. Una auditoría que enumera appRoleAssignments no ve nada del lado delegado, y viceversa: las dos consultas devuelven conjuntos disjuntos, así que una revisión que solo ejecuta una de ellas queda muda sobre la mitad de la superficie.
Las Dos Formas en que los Tenants Leen el Modelo de Consentimiento al Revés
«Lo delegado es lo seguro»
La frase «la aplicación no puede hacer más de lo que puede hacer el usuario» es cierta, y está cargando un peso para el que nunca fue diseñada.
Un otorgamiento delegado lleva un campo consentType con dos valores posibles. La referencia de Microsoft es inequívoca: «AllPrincipals indicates authorization to impersonate all users. Principal indicates authorization to impersonate a specific user.» Cuando consentType es AllPrincipals, principalId es nulo — el otorgamiento no está vinculado a nadie, porque se aplica a todos.
Un consentimiento admin a nivel de tenant para el permiso delegado Mail.ReadWrite no significa entonces que la aplicación pueda leer todo el correo. Significa que la aplicación está preautorizada para actuar sobre el buzón de cualquier usuario que inicie sesión en ella, sin pantalla de consentimiento, mientras el otorgamiento exista. El límite sigue siendo los propios privilegios del usuario conectado — pero la población es todo el directorio, y la fricción que normalmente obligaría a un usuario a detenerse y leer una pantalla de consentimiento ha desaparecido.
Considere ahora quién inicia sesión en las herramientas internas. Si un Global Administrator o un Exchange Administrator se autentica en una aplicación que posee un Directory.ReadWrite.All delegado a nivel de tenant, la aplicación hereda el alcance de esa sesión. Lo delegado no es un techo sobre el privilegio; es un techo que se mueve con quien esté conectado.
«Los permisos de aplicación son los poderosos, así que pidamos esos»
El error especular. El propio recorrido del portal de Microsoft presenta primero los permisos delegados y describe los permisos de aplicación como la opción para «service- or daemon-type applications that need to access a web API as themselves, without user interaction for sign-in or consent» — y esa descripción es exactamente por qué los desarrolladores los eligen. Un servicio que solo necesita leer un buzón compartido termina con Mail.Read contra toda la organización, porque esa es la única granularidad que Entra ID ofrece sobre el propio permiso de Graph.
La guía de Microsoft sobre acceso delegado es directa sobre qué escenario es cuál: el acceso delegado «usually a poor choice for scenarios that must run without a signed-in user, like automation», mientras que hay que «use delegated access whenever you want to let a signed-in user work with their own resources or resources they can access.» El modo de fallo en los tenants reales es elegir por comodidad — el que se autentica sin una ventana del navegador — en lugar de por qué principal realmente necesita el dato.
⚠️ Advertencia: los dos errores se suman. Un tenant que sobre-otorga tanto scopes delegados a nivel de tenant como permisos de aplicación sin acotar tiene dos caminos independientes y aditivos hacia el mismo dato, rastreados en dos lugares distintos.
Lo Que un Otorgamiento Delegado a Nivel de Tenant le Ofrece a un Atacante
El acceso delegado requiere dos autorizaciones independientes para tener éxito, y Microsoft las documenta por separado: la autorización de la aplicación cliente (la aplicación recibió el scope, que aterriza en la reclamación scp del token) y la autorización del usuario (el recurso verifica los propios derechos del usuario conectado). El ejemplo de OneDrive de Microsoft hace la matriz explícita — una solicitud falla si cualquiera de los dos el scope falta o el usuario no tiene derechos sobre el objeto.
Un atacante no necesita romper ese modelo. Necesita satisfacer las dos mitades a la vez, y un otorgamiento a nivel de tenant le entrega la primera mitad gratis:
- El scope ya está otorgado. Con
consentType = AllPrincipals, no hay ninguna pantalla de consentimiento que phishear ni ningún otorgamiento por usuario que crear. Cualquier token que la aplicación obtenga para cualquier usuario lleva el scope. - La mitad del usuario llega con la víctima. Todo lo que el usuario comprometido o phisheado pueda alcanzar, la aplicación ahora puede alcanzarlo vía la API, que es exactamente la diferencia entre «leer este único buzón en Outlook» y «enumerarlo programáticamente a velocidad de máquina». Y ese alcance suele ser más amplio de lo que sugiere el organigrama — vea roles admin de service principal sobreprivilegiados.
- La revocación no está donde la esperaría. La propia guía de Microsoft señala que en el centro de administración de Entra, «You can't revoke permissions in the User consent tab using the portal.» Los otorgamientos delegados por usuario deben eliminarse vía Graph o PowerShell.
Esta es la misma superficie de consentimiento abusada por el phishing de consentimiento OAuth, salvo que no se requiere ningún phishing — el otorgamiento ya está en su lugar, hecho por un administrador, generalmente años atrás, generalmente para una herramienta que sigue instalada.
Dos propiedades de app registration relacionadas amplían el radio de impacto. signInAudience configurado en AzureADMultipleOrgs hace que la app registration sea multi-tenant — «users with a Microsoft work or school account in any organization's Microsoft Entra tenant» pueden iniciar sesión. Y las aplicaciones que ya nadie usa conservan sus otorgamientos indefinidamente: el objeto oAuth2PermissionGrant no lleva ninguna propiedad de expiración, así que nada envejece un otorgamiento por sí solo, y una integración descomisionada con un secreto vivo y un otorgamiento a nivel de tenant vivo es una credencial esperando a ser encontrada. Vea también rotación de secretos de app registration en Entra.
Detección
Ejecute esto contra su propio tenant. Leer la actividad de inicio de sesión requiere AuditLog.Read.All (el menos privilegiado) y uno de los roles Reports Reader, Security Reader o Security Administrator; leer y revocar otorgamientos requiere al menos Cloud Application Administrator.
| Qué buscar | Dónde | Señal |
|---|---|---|
| Otorgamientos delegados a nivel de tenant | oauth2PermissionGrants | consentType eq 'AllPrincipals' — se aplica a todos los usuarios |
| Otorgamientos delegados por usuario | oauth2PermissionGrants | consentType eq 'Principal', no revocable en el portal |
| Permisos de aplicación | servicePrincipals/{id}/appRoleAssignments | Sin acotar, a nivel de tenant por construcción |
| Registros multi-tenant | applications | signInAudience eq 'AzureADMultipleOrgs' |
| Aplicaciones inactivas | /beta/reports/servicePrincipalSignInActivities | lastSignInActivity de más de 90 días |
Empiece por los otorgamientos que se aplican a todos. consentType admite $filter con eq:
GET https://graph.microsoft.com/v1.0/oauth2PermissionGrants?$filter=consentType eq 'AllPrincipals'
Cada resultado le da clientId (el object id del service principal cliente — no su appId), resourceId (la API llamada), y scope, una lista separada por espacios como openid User.Read GroupMember.Read.All. El equivalente en Microsoft Graph PowerShell:
Connect-MgGraph -Scopes "Directory.Read.All"
Get-MgOauth2PermissionGrant -All | Where-Object { $_.ConsentType -eq 'AllPrincipals' } |
Select-Object ClientId, ResourceId, Scope
Luego obtenga la otra colección para el mismo service principal, porque la consulta delegada de arriba nunca la mostrará:
GET https://graph.microsoft.com/v1.0/servicePrincipals/{id}/oauth2PermissionGrants
GET https://graph.microsoft.com/v1.0/servicePrincipals/{id}/appRoleAssignments
Registros multi-tenant — signInAudience admite $filter con eq, ne y not:
GET https://graph.microsoft.com/v1.0/applications?$filter=signInAudience eq 'AzureADMultipleOrgs'
Para aplicaciones inactivas, la actividad de inicio de sesión de service principals es una API beta — Microsoft indica que las API bajo /beta están sujetas a cambios y no están soportadas en producción, así que trátela como un insumo de auditoría, no como un control sobre el que construir. Admite $top, $filter y $orderby:
GET https://graph.microsoft.com/beta/reports/servicePrincipalSignInActivities
Import-Module Microsoft.Graph.Beta.Reports
Get-MgBetaReportServicePrincipalSignInActivity
Cada registro expone lastSignInActivity además de un desglose que indica cómo se usa la aplicación: delegatedClientSignInActivity (actúa para un usuario conectado) versus applicationAuthenticationClientSignInActivity (app-only, sin usuario). Ese desglose es la forma más rápida de detectar una aplicación que posee los dos tipos de permiso mientras solo ejerce uno de ellos, y alimenta directamente la detección de anomalías de inicio de sesión de service principal.
Remediación
💡 Quick Win: enumere primero consentType eq 'AllPrincipals' y revoque los otorgamientos vinculados a aplicaciones para las que nadie puede nombrar a un owner. Es el camino más corto entre la auditoría y un radio de impacto reducido.
1. Bloquear el sobre-consentimiento en la puerta. En el centro de administración de Microsoft Entra, vaya a Identity > Applications > Enterprise apps > Consent and permissions > User consent settings (Global Administrator para el portal; Privileged Role Administrator en caso contrario). Por defecto, «all users are allowed to consent to applications for permissions that don't require administrator consent.» Las dos policies integradas son microsoft-user-default-legacy (permite el consentimiento de usuario para apps — cualquier permiso que no requiera consentimiento admin, cualquier app) y microsoft-user-default-low (permite el consentimiento de usuario solo para apps de editores verificados o registradas en su tenant, y solo para permisos que haya clasificado como de bajo impacto). Programáticamente:
PATCH https://graph.microsoft.com/v1.0/policies/authorizationPolicy
{
"defaultUserRolePermissions": {
"permissionGrantPoliciesAssigned": [
"managePermissionGrantsForSelf.microsoft-user-default-low",
"managePermissionGrantsForOwnedResource.{other-current-policies}"
]
}
}
Lea primero la authorizationPolicy actual y preserve cualquier entrada ManagePermissionGrantsForOwnedResource.* existente, o eliminará silenciosamente el autoservicio de consentimiento de los desarrolladores. Combine esto con el flujo de consentimiento admin para que los usuarios puedan solicitar una revisión en lugar de quedar bloqueados, y revíselo junto con la otra configuración de usuario predeterminada del tenant que rige lo que los no-admins pueden crear.
⚠️ Advertencia: cambiar este ajuste solo corrige el futuro. Microsoft es explícito: «Any updates to user consent settings only affect future consent operations for applications. Existing consent grants remain unchanged.» Endurecer la policy no revoca ni un solo otorgamiento existente.
2. Revocar lo que ya existe. Los permisos delegados y de aplicación se eliminan mediante endpoints distintos:
DELETE https://graph.microsoft.com/v1.0/oAuth2PermissionGrants/{id}
DELETE https://graph.microsoft.com/v1.0/servicePrincipals/{resource-servicePrincipal-id}/appRoleAssignedTo/{appRoleAssignment-id}
Recuerde la salvedad del portal: la pestaña Admin consent permite revocar desde la UI, la pestaña User consent no. Y la revocación por sí sola no es duradera — Microsoft señala que «Revoking the current granted permission doesn't stop users from re-consenting to the application's requested permissions.» También hay que eliminar el permiso de lo que solicita la aplicación, o bloquear del todo el consentimiento de usuario.
3. Acotar los permisos de aplicación en lugar de aceptar el nivel de tenant. Para las cargas de trabajo de Exchange, RBAC for Applications permite emparejar un app role con un scope de recurso, de modo que Mail.Read cubra un conjunto filtrado de buzones en lugar de todos. Asignar estos roles requiere pertenecer al grupo de roles Organization Management en Exchange Online, y al rol Exchange Administrator en Microsoft Entra ID:
New-ServicePrincipal -AppId <appId> -ObjectId <servicePrincipalObjectId> -DisplayName "reporting-app"
New-ManagementScope -Name "Canadian users" -RecipientRestrictionFilter "CustomAttribute1 -eq '012332'"
New-ManagementRoleAssignment -App <servicePrincipalObjectId> -Role "Application Mail.Read" -CustomResourceScope "Canadian users"
Test-ServicePrincipalAuthorization -Identity "reporting-app" -Resource <targetMailbox> | Format-Table
Aquí hay una trampa que atrapa a casi todos. La FAQ de Microsoft lo dice sin rodeos: los permisos de las dos autoridades forman una unión, no una intersección. «If your Service Principal has Mail.Read granted in Microsoft Entra ID and you configure a resource-scoped Mail.Read permission in Application RBAC, it's important that you remove the assignment of Mail.Read from Microsoft Entra ID. Otherwise, the union ... results in no effective resource scoping.» Acotar en Exchange dejando el otorgamiento de Entra intacto no cambia nada. Note también que los cambios de permiso se cachean entre 30 minutos y 2 horas (el cmdlet de prueba evita el caché), y que los scopes de gestión exclusivos no restringen el acceso de la aplicación.
4. Retirar los registros inactivos. Cualquier app sin lastSignInActivity en 90 días es candidata a desactivar-y-luego-eliminar. Hágalo en ese orden para poder restaurarla rápido si un trabajo trimestral resulta depender de ella. Eliminar el service principal en Entra también elimina automáticamente sus asignaciones RBAC de Exchange.
5. Hacer la revisión recurrente, y cubrir las dos colecciones. Una revisión que solo lee appRoleAssignments dejará pasar un tenant cuya exposición real es un otorgamiento AllPrincipals de cinco años de antigüedad. Incorpore esto a la auditoría de seguridad de Entra ID más amplia en lugar de tratarlo como una limpieza puntual — los otorgamientos de consentimiento no tienen expiración, así que la deriva aquí es permanente hasta que alguien mire.
Cómo Detecta Esto EtcSec
El catálogo de Azure de EtcSec cubre las dos mitades del modelo de consentimiento. APP_EXCESSIVE_DELEGATED señala aplicaciones que poseen más scopes delegados de los que su función justifica, y APP_CONSENT_GRANTED_TENANT_WIDE saca a la luz los otorgamientos AllPrincipals que una revisión de permisos basada en app roles pasará por alto. APP_USER_CONSENT_UNRESTRICTED y APP_ADMIN_CONSENT_NOT_REQUIRED verifican los ajustes a nivel de tenant que permiten que esos otorgamientos se acumulen en primer lugar, mientras que APP_MULTI_TENANT identifica los registros con signInAudience extendido más allá de su propio directorio, y APP_UNUSED_90_DAYS lista los registros que aún poseen otorgamientos vivos mucho después de que alguien dejara de usarlos.
Para el lado de permisos de aplicación del mismo problema — específicamente los app roles Directory, Mail, Files y RoleManagement — vea Permisos Graph API peligrosos de app registration en Entra y Registros de aplicaciones Azure: apps sobreprivilegiadas del tenant.
ℹ️ Nota: EtcSec verifica estas malas configuraciones en cada auditoría de Entra ID. Ejecute una auditoría gratuita para ver cuáles de sus app registrations poseen hoy otorgamientos a nivel de tenant.
Fuentes
- Overview of permissions and consent in the Microsoft identity platform
- Microsoft identity platform delegated access scenario
- Web API app registration and API permissions (delegated and application permissions in the portal)
- oAuth2PermissionGrant resource type (Microsoft Graph v1.0)
- List oAuth2PermissionGrants (Microsoft Graph v1.0)
- application resource type — signInAudience (Microsoft Graph v1.0)
- Configure how users consent to applications
- Review permissions granted to enterprise applications
- servicePrincipalSignInActivity resource type (Microsoft Graph beta)
- List servicePrincipalSignInActivities (Microsoft Graph beta)
- Role Based Access Control for Applications in Exchange Online
Explore las páginas de identidad que apoyan este tema
