Tout tenant finit par devoir répondre à la question des permissions Graph API déléguées versus application Entra moindre privilège : lequel des deux modèles de consentement est réellement le plus sûr, et comment accorder à une application le minimum dont elle a besoin sans la casser ? La plupart des équipes répondent par instinct — les permissions déléguées « s'exécutent en tant qu'utilisateur », donc elles paraissent bornées ; les permissions d'application « s'exécutent en tant qu'application », donc elles paraissent dangereuses. Cet instinct a moitié raison, et la moitié où il a tort est celle que personne n'audite.
La permission qui donne discrètement à une application un point d'ancrage dans chaque boîte aux lettres du tenant n'est pas forcément une permission d'application. Ce peut être une permission déléguée qu'un administrateur a consentie au nom de toute l'organisation — et cet octroi vit dans une collection d'objets différente de celle des app roles, si bien qu'une revue des permissions d'application ne le remonte jamais.
Permissions Graph API Déléguées Versus Application Entra Moindre Privilège, Définition
La plateforme d'identité de Microsoft documente deux scénarios d'accès, et la distinction porte sur qui agit au nom de l'application, pas sur l'ampleur des dégâts qu'elle peut causer.
L'accès délégué signifie qu'un utilisateur s'est connecté et que l'application cliente accède à la ressource en son nom. Microsoft est explicite sur le plafond : « The application isn't able to access anything the signed in user couldn't access. » Une application avec la permission déléguée Files.Read.All « is only able to read files that the user can personally access. » Les permissions déléguées sont aussi appelées scopes, et l'objet d'octroi résultant est un oAuth2PermissionGrant.
L'accès application (app-only) signifie que l'application agit seule, sans utilisateur connecté — daemons, sauvegardes, automatisation. Ici, le plafond est la ressource elle-même : une application avec la permission d'application Microsoft Graph Files.Read.All « is able to read any file in the tenant using Microsoft Graph. » Les permissions d'application sont des app roles, l'objet d'octroi est un appRoleAssignment, et selon le tableau comparatif de Microsoft lui-même, seul un admin peut y consentir.
| Permissions déléguées | Permissions d'application | |
|---|---|---|
| Contexte d'accès | Au nom d'un utilisateur connecté | Sans utilisateur |
| Qui peut consentir | Les utilisateurs pour leurs propres données ; les admins pour tous les utilisateurs | Seul un admin |
| Méthodes de consentement | Statique (app registration) ou dynamique (demandé à la connexion) | Statique uniquement |
| Autres noms | Scopes, OAuth2 permission scopes | App roles, permissions app-only |
| Objet d'octroi | oAuth2PermissionGrant | appRoleAssignment |
Cette dernière ligne résume tout le problème. Les deux types de permissions vivent dans deux collections d'objets distinctes. Un audit qui énumère les appRoleAssignments ne voit rien du côté délégué, et inversement : les deux requêtes renvoient des ensembles disjoints, si bien qu'une revue qui n'en exécute qu'une seule reste muette sur la moitié de la surface.
Les Deux Façons dont les Tenants Lisent le Modèle de Consentement à l'Envers
« Le délégué est le sûr »
La phrase « l'application ne peut pas faire plus que ce que peut faire l'utilisateur » est vraie, et elle porte un poids qu'elle n'a jamais été conçue pour supporter.
Un octroi délégué porte un champ consentType à deux valeurs possibles. La référence de Microsoft est sans ambiguïté : « AllPrincipals indicates authorization to impersonate all users. Principal indicates authorization to impersonate a specific user. » Quand consentType vaut AllPrincipals, principalId est nul — l'octroi n'est rattaché à personne, parce qu'il s'applique à tout le monde.
Un consentement admin à l'échelle du tenant pour la permission déléguée Mail.ReadWrite ne signifie donc pas que l'application peut lire tous les mails. Cela signifie que l'application est pré-autorisée à agir sur la boîte aux lettres de n'importe quel utilisateur qui s'y connecte, sans invite de consentement, tant que l'octroi existe. La borne reste les propres privilèges de l'utilisateur connecté — mais la population est tout l'annuaire, et la friction qui obligerait normalement un utilisateur à s'arrêter et lire un écran de consentement a disparu.
Considérez maintenant qui se connecte aux outils internes. Si un Global Administrator ou un Exchange Administrator s'authentifie sur une application détenant un Directory.ReadWrite.All délégué à l'échelle du tenant, l'application hérite de la portée de cette session. Le délégué n'est pas un plafond sur le privilège ; c'est un plafond qui se déplace avec la personne connectée.
« Les permissions d'application sont les puissantes, donc demandons celles-là »
L'erreur en miroir. Le parcours du portail Microsoft présente d'abord les permissions déléguées et décrit les permissions d'application comme l'option pour les « service- or daemon-type applications that need to access a web API as themselves, without user interaction for sign-in or consent » — et cette description est exactement pourquoi les développeurs s'y jettent. Un service qui n'a besoin de lire qu'une seule boîte partagée finit par détenir Mail.Read sur toute l'organisation, parce que c'est la seule granularité qu'Entra ID offre sur la permission Graph elle-même.
Le mémo de Microsoft sur l'accès délégué est direct sur les scénarios : l'accès délégué est « usually a poor choice for scenarios that must run without a signed-in user, like automation », tandis qu'il faut « use delegated access whenever you want to let a signed-in user work with their own resources or resources they can access. » Le mode d'échec dans les vrais tenants est de choisir par confort — celui qui s'authentifie sans invite de navigateur — plutôt que selon le principal qui a réellement besoin de la donnée.
⚠️ Attention : les deux erreurs s'additionnent. Un tenant qui sur-accorde à la fois des scopes délégués à l'échelle du tenant et des permissions d'application non scopées a deux chemins indépendants et cumulatifs vers la même donnée, suivis dans deux endroits différents.
Ce Qu'un Octroi Délégué Tenant-Wide Offre à un Attaquant
L'accès délégué requiert deux autorisations indépendantes pour aboutir, et Microsoft les documente séparément : l'autorisation de l'application cliente (l'application a reçu le scope, qui atterrit dans la revendication scp du token) et l'autorisation de l'utilisateur (la ressource vérifie les droits propres de l'utilisateur connecté). L'exemple OneDrive de Microsoft rend la matrice explicite — une requête échoue si l'un ou l'autre le scope manque ou l'utilisateur n'a pas de droits sur l'objet.
Un attaquant n'a pas besoin de casser ce modèle. Il doit satisfaire les deux moitiés à la fois, et un octroi tenant-wide lui offre la première moitié gratuitement :
- Le scope est déjà accordé. Avec
consentType = AllPrincipals, il n'y a aucune invite de consentement à phisher et aucun octroi par utilisateur à créer. Tout token que l'application obtient pour n'importe quel utilisateur porte le scope. - La moitié utilisateur arrive avec la victime. Tout ce que l'utilisateur compromis ou phishé peut atteindre, l'application peut désormais l'atteindre via l'API, ce qui est exactement la différence entre « lire cette seule boîte dans Outlook » et « l'énumérer programmatiquement à vitesse machine ». Et cette portée est souvent plus large que ne le suggère l'organigramme — voir rôles admin de service principal sur-privilégiés.
- La révocation n'est pas où vous l'attendez. Le guide de Microsoft note que dans le centre d'administration Entra, « You can't revoke permissions in the User consent tab using the portal. » Les octrois délégués par utilisateur doivent être supprimés via Graph ou PowerShell.
C'est la même surface de consentement abusée par le phishing de consentement OAuth, sauf qu'aucun phishing n'est requis — l'octroi est déjà en place, fait par un administrateur, généralement des années plus tôt, généralement pour un outil encore installé.
Deux propriétés d'app registration liées élargissent le rayon d'impact. signInAudience positionné sur AzureADMultipleOrgs rend l'app registration multi-tenant — « users with a Microsoft work or school account in any organization's Microsoft Entra tenant » peuvent se connecter. Et les applications que plus personne n'utilise conservent leurs octrois indéfiniment : l'objet oAuth2PermissionGrant ne porte aucune propriété d'expiration, donc rien ne fait vieillir un octroi tout seul, et une intégration décommissionnée avec un secret vivant et un octroi tenant-wide vivant est un identifiant qui attend d'être trouvé. Voir aussi rotation des secrets d'app registration Entra.
Détection
Exécutez ceci sur votre propre tenant. Lire l'activité de connexion nécessite AuditLog.Read.All (le moins privilégié) et l'un des rôles Reports Reader, Security Reader ou Security Administrator ; lire et révoquer des octrois nécessite au minimum Cloud Application Administrator.
| Ce qu'il faut chercher | Où | Signal |
|---|---|---|
| Octrois délégués tenant-wide | oauth2PermissionGrants | consentType eq 'AllPrincipals' — s'applique à tous les utilisateurs |
| Octrois délégués par utilisateur | oauth2PermissionGrants | consentType eq 'Principal', non révocable dans le portail |
| Permissions d'application | servicePrincipals/{id}/appRoleAssignments | Non scopées, tenant-wide par construction |
| App registrations multi-tenant | applications | signInAudience eq 'AzureADMultipleOrgs' |
| Applications dormantes | /beta/reports/servicePrincipalSignInActivities | lastSignInActivity de plus de 90 jours |
Commencez par les octrois qui s'appliquent à tout le monde. consentType supporte $filter avec eq :
GET https://graph.microsoft.com/v1.0/oauth2PermissionGrants?$filter=consentType eq 'AllPrincipals'
Chaque résultat donne clientId (l'object id du service principal client — pas son appId), resourceId (l'API appelée), et scope, une liste séparée par des espaces telle que openid User.Read GroupMember.Read.All. L'équivalent en Microsoft Graph PowerShell :
Connect-MgGraph -Scopes "Directory.Read.All"
Get-MgOauth2PermissionGrant -All | Where-Object { $_.ConsentType -eq 'AllPrincipals' } |
Select-Object ClientId, ResourceId, Scope
Récupérez ensuite l'autre collection pour le même service principal, parce que la requête déléguée ci-dessus ne la montrera jamais :
GET https://graph.microsoft.com/v1.0/servicePrincipals/{id}/oauth2PermissionGrants
GET https://graph.microsoft.com/v1.0/servicePrincipals/{id}/appRoleAssignments
App registrations multi-tenant — signInAudience supporte $filter avec eq, ne et not :
GET https://graph.microsoft.com/v1.0/applications?$filter=signInAudience eq 'AzureADMultipleOrgs'
Pour les applications dormantes, l'activité de connexion des service principals est une API beta — Microsoft précise que les API sous /beta sont sujettes à changement et ne sont pas supportées en production ; traitez-la comme un input d'audit, pas comme un contrôle sur lequel bâtir. Elle supporte $top, $filter et $orderby :
GET https://graph.microsoft.com/beta/reports/servicePrincipalSignInActivities
Import-Module Microsoft.Graph.Beta.Reports
Get-MgBetaReportServicePrincipalSignInActivity
Chaque enregistrement expose lastSignInActivity ainsi qu'une ventilation qui indique comment l'application est utilisée : delegatedClientSignInActivity (elle agit pour un utilisateur connecté) versus applicationAuthenticationClientSignInActivity (app-only, sans utilisateur). Cette ventilation est le moyen le plus rapide de repérer une application qui détient les deux types de permission tout en n'exerçant jamais qu'un seul, et cela alimente directement la détection d'anomalies de connexion de service principal.
Remédiation
💡 Quick Win : énumérez d'abord consentType eq 'AllPrincipals' et révoquez les octrois rattachés à des applications dont personne ne peut nommer un owner. C'est le chemin le plus court entre l'audit et un rayon d'impact réduit.
1. Bloquer le sur-consentement à la porte. Dans le centre d'administration Microsoft Entra, allez dans Identity > Applications > Enterprise apps > Consent and permissions > User consent settings (Global Administrator pour le portail ; Privileged Role Administrator sinon). Par défaut, « all users are allowed to consent to applications for permissions that don't require administrator consent. » Les deux policies intégrées sont microsoft-user-default-legacy (autorise le consentement utilisateur pour les apps — toute permission ne nécessitant pas de consentement admin, toute app) et microsoft-user-default-low (autorise le consentement utilisateur seulement pour les apps d'éditeurs vérifiés ou enregistrées dans votre tenant, et seulement pour les permissions que vous avez classées à faible impact). Par programmation :
PATCH https://graph.microsoft.com/v1.0/policies/authorizationPolicy
{
"defaultUserRolePermissions": {
"permissionGrantPoliciesAssigned": [
"managePermissionGrantsForSelf.microsoft-user-default-low",
"managePermissionGrantsForOwnedResource.{other-current-policies}"
]
}
}
Lisez d'abord la authorizationPolicy actuelle et préservez toute entrée ManagePermissionGrantsForOwnedResource.* existante, sinon vous supprimerez silencieusement le libre-service de consentement des développeurs. Associez ceci au workflow de consentement admin pour que les utilisateurs puissent demander une revue plutôt que d'être bloqués, et revoyez-le avec les autres paramètres utilisateur par défaut du tenant qui régissent ce que les non-admins peuvent créer.
⚠️ Attention : changer ce paramètre ne corrige que l'avenir. Microsoft est explicite : « Any updates to user consent settings only affect future consent operations for applications. Existing consent grants remain unchanged. » Durcir la policy ne révoque pas un seul octroi existant.
2. Révoquer ce qui existe déjà. Les permissions déléguées et d'application se suppriment via des endpoints différents :
DELETE https://graph.microsoft.com/v1.0/oAuth2PermissionGrants/{id}
DELETE https://graph.microsoft.com/v1.0/servicePrincipals/{resource-servicePrincipal-id}/appRoleAssignedTo/{appRoleAssignment-id}
N'oubliez pas la nuance du portail : l'onglet Admin consent supporte la révocation dans l'UI, l'onglet User consent non. Et la révocation seule n'est pas durable — Microsoft note que « Revoking the current granted permission doesn't stop users from re-consenting to the application's requested permissions. » Il faut aussi retirer la permission de ce que demande l'application, ou bloquer totalement le consentement utilisateur.
3. Scoper les permissions d'application au lieu d'accepter le tenant-wide. Pour les workloads Exchange, RBAC for Applications permet d'associer un app role à un scope de ressource, de sorte que Mail.Read couvre un ensemble filtré de boîtes plutôt que toutes. Assigner ces rôles nécessite l'appartenance au groupe de rôles Organization Management dans Exchange Online, et le rôle Exchange Administrator dans 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
Il y a un piège ici qui attrape presque tout le monde. La FAQ de Microsoft le dit noir sur blanc : les permissions des deux autorités forment une union, pas une intersection. « 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. » Scoper dans Exchange en laissant l'octroi Entra en place ne change rien. Notez aussi que les changements de permission sont mis en cache entre 30 minutes et 2 heures (le cmdlet de test contourne le cache), et que les scopes de gestion exclusifs ne restreignent pas l'accès applicatif.
4. Retirer les app registrations dormantes. Toute app sans lastSignInActivity depuis 90 jours est candidate à un désactiver-puis-supprimer. Faites-le dans cet ordre pour pouvoir restaurer rapidement si un job trimestriel s'avère en dépendre. Supprimer le service principal dans Entra retire aussi automatiquement ses assignations RBAC Exchange.
5. Rendre la revue récurrente, et couvrir les deux collections. Une revue qui ne lit que appRoleAssignments laissera passer un tenant dont l'exposition réelle est un octroi AllPrincipals vieux de cinq ans. Intégrez ceci au plus large audit de sécurité Entra ID plutôt que de le traiter comme un nettoyage ponctuel — les octrois de consentement n'ont pas d'expiration, donc la dérive ici est permanente tant que personne ne regarde.
Comment EtcSec Détecte Cela
Le catalogue Azure d'EtcSec couvre les deux moitiés du modèle de consentement. APP_EXCESSIVE_DELEGATED signale les applications détenant plus de scopes délégués que leur fonction ne le justifie, et APP_CONSENT_GRANTED_TENANT_WIDE fait remonter les octrois AllPrincipals qu'une revue de permissions calée sur les app roles ratera. APP_USER_CONSENT_UNRESTRICTED et APP_ADMIN_CONSENT_NOT_REQUIRED vérifient les réglages au niveau du tenant qui laissent ces octrois s'accumuler en premier lieu, tandis que APP_MULTI_TENANT identifie les app registrations avec un signInAudience étendu au-delà de votre propre annuaire, et APP_UNUSED_90_DAYS liste les app registrations qui détiennent encore des octrois vivants longtemps après que quiconque a cessé de les utiliser.
Pour le versant permissions d'application du même problème — les app roles Directory, Mail, Files et RoleManagement spécifiquement — voir Permissions Graph API dangereuses d'app registration Entra et App registrations Azure : applications sur-privilégiées du tenant.
ℹ️ Remarque : EtcSec vérifie ces mauvaises configurations à chaque audit Entra ID. Lancez un audit gratuit pour voir lesquelles de vos app registrations détiennent aujourd'hui des octrois tenant-wide.
Sources
- 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
Explorez les pages identité liées à ce sujet
