Skip to content

Find service principals holding high-privilege directory roles.

Service principals (app registrations) can be granted built-in directory roles such as Global Administrator. This script enumerates service principals and reports the ones holding a high-privilege role assignment.

Permissions

Requires delegated permissions Directory.Read.All and AppRoleAssignment.Read.All.

Reference

View source

import argparse

from office365.graph_client import GraphClient
from tests import test_client_id, test_client_secret, test_tenant

# Resource display names of the directory roles application (formerly Azure AD).
DIRECTORY_ROLE_RESOURCES = {"Microsoft Entra ID", "Microsoft Azure Active Directory"}

# Built-in directory role appRoleIds (on the "Microsoft Entra ID" service
# principal) considered high privilege when granted to a service principal.
HIGH_PRIVILEGE_ROLES = {
    "62e90394-69f5-4237-9190-012177145e10": "Global Administrator",
    "e8611ab3-c189-46e8-94e1-60213ab1f814": "Privileged Role Administrator",
    "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3": "Application Administrator",
    "158c047a-c907-4556-b7ef-446551a6b5f7": "Cloud Application Administrator",
    "29232cdf-8323-42fc-9f8e-4f9b9c3d4e5f": "Exchange Administrator",
    "f28a1f50-f6e7-4571-818b-13512edef760": "SharePoint Administrator",
    "fe930be7-5e62-47db-91af-98c3a49a38b1": "User Administrator",
}


def main():
    parser = argparse.ArgumentParser(description="Service principals holding high-privilege directory roles")
    parser.add_argument(
        "--all-roles",
        action="store_true",
        help="report every directory-role assignment, not only high-privilege roles",
    )
    args = parser.parse_args()

    client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)

    findings = []
    for sp in client.service_principals.select(["id", "displayName"]).get().execute_query():
        roles = set()
        for assignment in sp.app_role_assignments.get().execute_query():
            if assignment.resource_display_name not in DIRECTORY_ROLE_RESOURCES:
                continue
            app_role_id = assignment.app_role_id or ""
            role = HIGH_PRIVILEGE_ROLES.get(app_role_id, app_role_id)
            if args.all_roles or role in HIGH_PRIVILEGE_ROLES.values():
                roles.add(role)
        if roles:
            findings.append((sp.display_name, roles))

    if not findings:
        print("No service principals with directory-role assignments found.")
        return

    for name, roles in findings:
        print(f"  {name:50s} {', '.join(sorted(roles))}")
    print(f"\n{len(findings)} service principal(s) hold directory roles")


if __name__ == "__main__":
    main()

← Back to Application Registration & Permissions