prowler-cloud/prowler
View on GitHub[New Check]: Service principals with privileged Entra directory roles must not have owners
Open
#11070 opened on May 6, 2026
feature-requestgood first issuenew-checkprovider/m365
Description
Existing check search
- I have searched existing issues, Prowler Hub, and the public roadmap, and this check does not already exist.
Provider
Microsoft 365
New provider name
No response
Service or product area
entra
Suggested check name
entra_service_principal_privileged_role_no_owners
Context and goal
- Security condition to validate: No service principal that holds a privileged Microsoft Entra directory role (Global Administrator, Application Administrator, Privileged Role Administrator, Privileged Authentication Administrator, Cloud Application Administrator, Hybrid Identity Administrator, etc.) has any owner — neither on the service principal itself nor on its parent application registration.
- Why it matters: An owner of a service principal or app registration can rotate its credentials, add new ones (
passwordCredentials/keyCredentials/federatedIdentityCredentials), or transfer ownership. If the underlying app holds a privileged directory role, the owner effectively becomes a back-door path into that role: they can mint a fresh secret, sign in as the app, and inherit Global Admin (or equivalent) privileges — all outside Privileged Identity Management approval flows and often outside Conditional Access scrutiny aimed at user accounts. Microsoft documents this as a known privilege-escalation pattern; the recommendation is that any service principal with a privileged role be ownerless and managed exclusively via PIM-eligible role assignments and break-glass controls. - Resource involved: Microsoft Entra service principals and applications, plus their
roleAssignmentsagainst the unifiedRoleManagement API.
Expected behavior
- Resource or scope to evaluate:
- List directory role assignments via
GET /roleManagement/directory/roleAssignments?$expand=principaland keep entries whoseprincipal.@odata.type = #microsoft.graph.servicePrincipal. - Restrict to privileged roles. Use a hardcoded constant of well-known privileged role template IDs (Global Administrator
62e90394-69f5-4237-9190-012177145e10, Application Administrator9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3, Privileged Role Administratore8611ab8-c189-46e8-94e1-60213ab1f814, Privileged Authentication Administrator7be44c8a-adaf-4e2a-84d6-ab2649e08a13, Cloud Application Administrator158c047a-c907-4556-b7ef-446551a6b5f7, Hybrid Identity Administrator8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2, Authentication Administratorc4e39bd9-1100-46d3-8c65-fb160da0071f, Conditional Access Administratorb1be1c3e-b65d-4f19-8427-f6fa0d97feb9, Security Administrator194ae4cb-b126-40b2-bd5b-6091b380977d, User Administratorfe930be7-5e62-47db-91af-98c3a49a38b1). - For each matching service principal, fetch its owners (
GET /servicePrincipals/{id}/owners) and the owners of its parent application (GET /applications/{appObjectId}/owners) when an in-tenantapplicationexists for the sameappId.
- List directory role assignments via
- PASS when: every privileged-role service principal has zero owners on both the service principal and the parent application.
- FAIL when: at least one privileged-role service principal has at least one owner on either the service principal or the parent application. The finding should report the service principal
displayName,appId, the privileged role(s) it holds, and the owner principal IDs. - MANUAL when: the calling identity does not have permission to enumerate role assignments or owners (Graph 403). Mark MANUAL with a status message asking the operator to grant the required scopes.
- Exclusions / edge cases:
- Skip Microsoft first-party service principals (those whose
appOwnerOrganizationIdmatches the well-known Microsoft tenant IDs). They are managed by Microsoft and their owner model is out of customer control. - Disabled service principals (
accountEnabled = false) are out of scope. - The hardcoded privileged-role list should live as a module-level constant so it is easy to extend if Microsoft introduces new privileged roles.
- Skip Microsoft first-party service principals (those whose
References
- Microsoft Graph v1.0 —
roleManagement/directory/roleAssignments: https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleassignments?view=graph-rest-1.0 - Microsoft Graph v1.0 — List
servicePrincipalowners: https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-owners?view=graph-rest-1.0 - Microsoft Graph v1.0 — List
applicationowners: https://learn.microsoft.com/en-us/graph/api/application-list-owners?view=graph-rest-1.0 - Microsoft Entra — Built-in roles permissions reference (privileged role taxonomy): https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference
- Microsoft Entra — Understand Microsoft Entra role concepts: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/concept-understand-roles
Suggested severity
High
Additional implementation notes
- Existing patterns to follow: Reuse the service-principal enumeration pattern from
entra_app_registration_no_unused_privileged_permissions(prowler/providers/m365/services/entra/). Extendentra_service.pyso theServicePrincipalandApplicationmodels expose anownerscollection (a list of principal IDs is enough for the check). - Permissions / scopes: No additional permissions beyond Prowler's M365 baseline.
Directory.Read.Allalready grants reads againstroleManagement/directory/roleAssignments,servicePrincipals/{id}/owners, andapplications/{id}/owners. - PowerShell is NOT needed; the check uses Microsoft Graph v1.0 only.
- Privileged roles constant: keep the list explicit and short. Do NOT pull every role tagged as
isPrivileged = truefrom/roleManagement/directory/roleDefinitionsat runtime — that surface drifts as Microsoft adds roles, and a check that quietly broadens scope is harder to reason about in noise/triage. A static, reviewable list is the right trade-off here. - Related check (do NOT duplicate, complementary):
entra_app_registration_no_unused_privileged_permissionsaudits unused API permissions (Microsoft Graph delegated/application permissions). This new check audits directory role assignments, which is a different surface (Entra RBAC vs Graph permissions). - Metadata: follow the M365 metadata schema used by sibling checks under
entra/.