Finding synced accounts with Azure AD privileges

In a recent blogpost, Protecting Microsoft 365 from on-premises attacks, it is recommended to not federate (which I have been recommending for years) and not to synchronize accounts with privileged access to Azure AD and Microsoft 365. This post contains a few script to help you determine synchronized accounts with privileged access to Azure AD.

As usual, I skip the whole “Get yourself an access token”-part of the script, for simplicity. In short words:

  • Go to Graph Explorer
  • Sign in with your admin account
  • Click Select Permissions:
  • Make sure the following consents are in place:
    • Group.Read.All
    • User.Read.All
    • Directory.AccessAsUser.All
    • PrivilegedAccess.Read.AzureAD
    • RoleManagement.Read.Directory
  • Copy the access token like this, into the $accessToken variable in the scripts

There are ways to automate all of this, of course.

First script – For those without Azure AD Privileged Identity Management:

The script uses the /v1.0/directoryRoles endpoint for getting directory role assignments. This is valid for all tenants, but does not return eligible assignments. Hence, only use this if you do not use PIM:

$accessToken = "eyJ0eXAiOiJKV1Q---- snip ---dtEiPuNZ8bA"
$Script:Headers = @{Authorization = "Bearer $accessToken"}

# Function that returns all role members using the directoryRoles MS Graph endpoint
function Get-AADDirectoryRoleMember
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$true,
            Position=0)]
        [String] $RoleTemplateId
    )

    Process
    {
        try {
            Write-Debug "Getting directory role id for role template id $RoleTemplateId"
            $result = Invoke-RestMethod "https://graph.microsoft.com/v1.0/directoryRoles?`$filter=roleTemplateId eq '$RoleTemplateId'" -Headers $Script:Headers

            if(($result.value|Measure-Object).Count -eq 0) {
                return;
            }

            $directoryRoleId = $result.value[0].id
            $directoryRoleDisplayName = $result.value[0].displayName
            Write-Debug "Found role $directoryRoleDisplayName"

            # Get members and count them
            Write-Debug "Counting members"
            $result = Invoke-RestMethod "https://graph.microsoft.com/v1.0/directoryRoles/$directoryRoleId/members?`$select=id" -Headers $Script:Headers

            Write-Debug "Getting users assigned to role, with some details"
            $result.value | ForEach-Object {
                $assignment = $_
                $uri = "https://graph.microsoft.com/beta/directoryObjects/$($assignment.id)"
                $_r = Invoke-RestMethod $uri -Headers $Script:Headers
                if($_r.'@odata.type' -eq "#microsoft.graph.group") {
                    $uri = "https://graph.microsoft.com/beta/groups/$($assignment.id)/transitiveMembers?`$top=999"
                    $_members = $null
                    $_members = Invoke-RestMethod $uri -Headers $Script:Headers
                    $_members.value | ForEach-Object {
                        [PSCustomObject] @{
                            objectId = $_.id
                            userType = $_.userType
                            upn = $_.userPrincipalName
                            roleTemplateId = $RoleTemplateId
                            directoryRoleDisplayName = $directoryRoleDisplayName
                            synched = $_.onPremisesDistinguishedName -or $_.onPremisesSyncEnabled
                        }
                    }
                } else{
                    [PSCustomObject] @{
                        objectId = $_r.id
                        userType = $_r.userType
                        upn = $_r.userPrincipalName
                        roleTemplateId = $RoleTemplateId
                        directoryRoleDisplayName = $directoryRoleDisplayName
                        synched = $_.onPremisesDistinguishedName -or $_.onPremisesSyncEnabled
                    }
                }
            }
        } catch {
            throw "Error occured processing role '$RoleTemplateId': $_"
        }
    }
}

$RoleTemplateIds =
    "9B895D92-2CD3-44C7-9D02-A6AC2D5EA5C3",#	ApplicationAdministrator
    "CF1C38E5-3621-4004-A7CB-879624DCED7C",#	ApplicationDeveloper
    "c4e39bd9-1100-46d3-8c65-fb160da0071f",#	AuthenticationAdministrator
    "7495fdc4-34c4-4d15-a289-98788ce399fd",#	AzureInformationProtectionAdministrator
    "aaf43236-0c0d-4d5f-883a-6955382ac081",#	B2CIEFKeysetAdministrator
    "3edaf663-341e-4475-9f94-5c398ef6c070",#	B2CIEFPolicyAdministrator
    "158c047a-c907-4556-b7ef-446551a6b5f7",#	CloudApplicationAdministrator
    "7698a772-787b-4ac8-901f-60d6b08affd2",#	CloudDeviceAdministrator
    "62e90394-69f5-4237-9190-012177145e10",#	CompanyAdministrator
    "17315797-102d-40b4-93e0-432062caca18",#	ComplianceAdministrator
    "e6d1a23a-da11-4be4-9570-befc86d067a7",#	ComplianceDataAdministrator
    "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9",#	ConditionalAccessAdministrator
    "5c4f9dcd-47dc-4cf7-8c9a-9e4207cbfc91",#	CustomerLockBoxAccessApprover
    "38a96431-2bdf-4b4c-8b6e-5d3d8abac1a4",#	DesktopAnalyticsAdministrator
    "9f06204d-73c1-4d4c-880a-6edb90606fd8",#	DeviceAdministrators
    "9360feb5-f418-4baa-8175-e2a00bac4301",#	DirectoryWriters
    "29232cdf-9323-42fd-ade2-1d097af3e4de",#	ExchangeServiceAdministrator
    "6e591065-9bad-43ed-90f3-e9424366d2f0",#	ExternalIdUserflowAdministrator
    "0f971eea-41eb-4569-a71e-57bb8a3eff1e",#	ExternalIdUserFlowAttributeAdministrator
    "be2f45a1-457d-42af-a067-6ec1fa63bc45",#	ExternalIdentityProviderAdministrator
    "f2ef992c-3afb-46b9-b7cf-a126ee74c451",#	GlobalReader
    "fdd7a751-b60b-444a-984c-02652fe8fa1c",#	GroupsAdministrator
    "729827e3-9c14-49f7-bb1b-9608f156bbb8",#	HelpdeskAdministrator
    "8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2",#	HybridIdentityAdministrator
    "3a2c62db-5318-420d-8d74-23affee5d9d5",#	IntuneServiceAdministrator
    "4d6ac14f-3453-41d0-bef9-a3e0c569773a",#	LicenseAdministrator
    "75941009-915a-4869-abe7-691bff18279e",#	LyncServiceAdministrator
    "ac16e43d-7b2d-40e0-ac05-243ff356ab5b",#	MessageCenterPrivacyReader
    "790c1fb9-7f7d-4f88-86a1-ef1f95c05c1b",#	MessageCenterReader
    "d37c8bed-0711-4417-ba38-b4abe66ce4c2",#	NetworkAdministrator
    "2b745bdf-0803-4d80-aa65-822c4493daac",#	OfficeAppsAdministrator
    "966707d0-3269-4727-9be2-8c3a10f19b9d",#	PasswordAdministrator
    "11648597-926c-4cf3-9c36-bcebb0ba8dcc",#	PowerPlatformAdministrator
    "7be44c8a-adaf-4e2a-84d6-ab2649e08a13",#	PrivilegedAuthenticationAdministrator
    "e8611ab8-c189-46e8-94e1-60213ab1f814",#	PrivilegedRoleAdministrator
    "4a5d8f65-41da-4de4-8968-e035b65339cf",#	ReportsReader
    "0964bb5e-9bdb-4d7b-ac29-58e794862a40",#	SearchAdministrator
    "8835291a-918c-4fd7-a9ce-faa49f0cf7d9",#	SearchEditor
    "194ae4cb-b126-40b2-bd5b-6091b380977d",#	SecurityAdministrator
    "5f2222b1-57c3-48ba-8ad5-d4759f1fde6f",#	SecurityOperator
    "5d6b6bb7-de71-4623-b4af-96380a352509",#	SecurityReader
    "f023fd81-a637-4b56-95fd-791ac0226033",#	ServiceSupportAdministrator
    "f28a1f50-f6e7-4571-818b-6a12f2af6b6c",#	SharePointServiceAdministrator
    "baf37b3a-610e-45da-9e62-d9d1e5e8914b",#	TeamsCommunicationsAdministrator
    "f70938a0-fc10-4177-9e90-2178f8765737",#	TeamsCommunicationsSupportEngineer
    "fcf91098-03e3-41a9-b5ba-6f0ec8188a12",#	TeamsCommunicationsSupportSpecialist
    "3d762c5a-1b6c-493f-843e-55a3b42923d4",#	TeamsDevicesAdministrator
    "69091246-20e8-4a56-aa4d-066075b2a7a8",#	TeamsServiceAdministrator
    "fe930be7-5e62-47db-91af-98c3a49a38b1" #	UserAccountAdministrator

$RoleTemplateIds |
    Get-AADDirectoryRoleMember |
    # Where-Object synched |
    Out-GridView

The output contains a column / property telling you whether the account is synched or not. Follow up anyone that is synched, to make sure they use cloud managed accounts instead!

Second script – For those with Azure AD Privileged Identity Management:

The script uses the newer /beta/privilegedAccess/aadroles/roleAssignments endpoint, which only works for Azure AD Premium P2 enabled tenants, and works for eligible assignments as well as active assignments.

$accessToken = "eyJ0eXAiOiJ---snip---EiPuNZ8bA"
$Script:Headers = @{Authorization = "Bearer $accessToken"}

function Get-AADPIMRoleMember
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$true,
            Position=0)]
        [String] $RoleTemplateId
    )

    Process
    {
        try {
            # Get TenantID from payload
            $jwtPayload = $Script:Headers.Authorization -replace "Bearer " -split "\." | Select-Object -Index 1
            if($jwtPayload.Length % 4 -ne 0) {
                $jwtPayload = $jwtPayload.PadRight($jwtPayload.Length + 4 - $jwtPayload.Length % 4, "=")
            }
            $Script:TenantId = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($jwtPayload)) | ConvertFrom-Json | Select-Object -ExpandProperty tid 

            Write-Debug "Getting directory role id for role template id $RoleTemplateId"
            $result = Invoke-RestMethod "https://graph.microsoft.com/v1.0/directoryRoles?`$filter=roleTemplateId eq '$RoleTemplateId'" -Headers $Script:Headers

            if(($result.value|Measure-Object).Count -eq 0) {
                return;
            }

            # $directoryRoleId = $result.value[0].id
            $directoryRoleDisplayName = $result.value[0].displayName
            Write-Debug "Found role $directoryRoleDisplayName"

            Write-Debug "Getting PIM role for role template id $RoleTemplateId"
            $uri = "https://graph.microsoft.com/beta/privilegedAccess/aadroles/roleAssignments?`$filter=resourceId+eq+'$Script:TenantID' and roleDefinitionId+eq+'$RoleTemplateId'&`$expand=roleDefinition(`$expand=resource)" # $expand=linkedEligibleRoleAssignment,subject,scopedResource,roleDefinition($expand=resource)
            $result = Invoke-RestMethod $uri -Headers $Script:Headers

            Write-Debug "Getting users assigned to role, with some details"
            $result.value | ForEach-Object {
                $assignment = $_
                $uri = "https://graph.microsoft.com/beta/directoryObjects/$($assignment.subjectId)"
                $_r = Invoke-RestMethod $uri -Headers $Script:Headers
                if($_r.'@odata.type' -eq "#microsoft.graph.group") {
                    $uri = "https://graph.microsoft.com/beta/groups/$($assignment.subjectId)/transitiveMembers?`$top=999"
                    $_members = $null
                    $_members = Invoke-RestMethod $uri -Headers $Script:Headers
                    $_members.value | ForEach-Object {
                        [PSCustomObject] @{
                            objectId = $_.id
                            userType = $_.userType
                            upn = $_.userPrincipalName
                            roleTemplateId = $RoleTemplateId
                            directoryRoleDisplayName = $directoryRoleDisplayName
                            synched = $_.onPremisesDistinguishedName -or $_.onPremisesSyncEnabled
                        }
                    }
                } else{
                    [PSCustomObject] @{
                        objectId = $_r.id
                        userType = $_r.userType
                        upn = $_r.userPrincipalName
                        roleTemplateId = $RoleTemplateId
                        directoryRoleDisplayName = $directoryRoleDisplayName
                        synched = $_.onPremisesDistinguishedName -or $_.onPremisesSyncEnabled
                    }
                }
            }
        } catch {
            throw "Error occured processing role '$RoleTemplateId': $_"
        }
    }
}

$RoleTemplateIds =
    "9B895D92-2CD3-44C7-9D02-A6AC2D5EA5C3",#	ApplicationAdministrator
    "CF1C38E5-3621-4004-A7CB-879624DCED7C",#	ApplicationDeveloper
    "c4e39bd9-1100-46d3-8c65-fb160da0071f",#	AuthenticationAdministrator
    "7495fdc4-34c4-4d15-a289-98788ce399fd",#	AzureInformationProtectionAdministrator
    "aaf43236-0c0d-4d5f-883a-6955382ac081",#	B2CIEFKeysetAdministrator
    "3edaf663-341e-4475-9f94-5c398ef6c070",#	B2CIEFPolicyAdministrator
    "158c047a-c907-4556-b7ef-446551a6b5f7",#	CloudApplicationAdministrator
    "7698a772-787b-4ac8-901f-60d6b08affd2",#	CloudDeviceAdministrator
    "62e90394-69f5-4237-9190-012177145e10",#	CompanyAdministrator
    "17315797-102d-40b4-93e0-432062caca18",#	ComplianceAdministrator
    "e6d1a23a-da11-4be4-9570-befc86d067a7",#	ComplianceDataAdministrator
    "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9",#	ConditionalAccessAdministrator
    "5c4f9dcd-47dc-4cf7-8c9a-9e4207cbfc91",#	CustomerLockBoxAccessApprover
    "38a96431-2bdf-4b4c-8b6e-5d3d8abac1a4",#	DesktopAnalyticsAdministrator
    "9f06204d-73c1-4d4c-880a-6edb90606fd8",#	DeviceAdministrators
    "9360feb5-f418-4baa-8175-e2a00bac4301",#	DirectoryWriters
    "29232cdf-9323-42fd-ade2-1d097af3e4de",#	ExchangeServiceAdministrator
    "6e591065-9bad-43ed-90f3-e9424366d2f0",#	ExternalIdUserflowAdministrator
    "0f971eea-41eb-4569-a71e-57bb8a3eff1e",#	ExternalIdUserFlowAttributeAdministrator
    "be2f45a1-457d-42af-a067-6ec1fa63bc45",#	ExternalIdentityProviderAdministrator
    "f2ef992c-3afb-46b9-b7cf-a126ee74c451",#	GlobalReader
    "fdd7a751-b60b-444a-984c-02652fe8fa1c",#	GroupsAdministrator
    "729827e3-9c14-49f7-bb1b-9608f156bbb8",#	HelpdeskAdministrator
    "8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2",#	HybridIdentityAdministrator
    "3a2c62db-5318-420d-8d74-23affee5d9d5",#	IntuneServiceAdministrator
    "4d6ac14f-3453-41d0-bef9-a3e0c569773a",#	LicenseAdministrator
    "75941009-915a-4869-abe7-691bff18279e",#	LyncServiceAdministrator
    "ac16e43d-7b2d-40e0-ac05-243ff356ab5b",#	MessageCenterPrivacyReader
    "790c1fb9-7f7d-4f88-86a1-ef1f95c05c1b",#	MessageCenterReader
    "d37c8bed-0711-4417-ba38-b4abe66ce4c2",#	NetworkAdministrator
    "2b745bdf-0803-4d80-aa65-822c4493daac",#	OfficeAppsAdministrator
    "966707d0-3269-4727-9be2-8c3a10f19b9d",#	PasswordAdministrator
    "11648597-926c-4cf3-9c36-bcebb0ba8dcc",#	PowerPlatformAdministrator
    "7be44c8a-adaf-4e2a-84d6-ab2649e08a13",#	PrivilegedAuthenticationAdministrator
    "e8611ab8-c189-46e8-94e1-60213ab1f814",#	PrivilegedRoleAdministrator
    "4a5d8f65-41da-4de4-8968-e035b65339cf",#	ReportsReader
    "0964bb5e-9bdb-4d7b-ac29-58e794862a40",#	SearchAdministrator
    "8835291a-918c-4fd7-a9ce-faa49f0cf7d9",#	SearchEditor
    "194ae4cb-b126-40b2-bd5b-6091b380977d",#	SecurityAdministrator
    "5f2222b1-57c3-48ba-8ad5-d4759f1fde6f",#	SecurityOperator
    "5d6b6bb7-de71-4623-b4af-96380a352509",#	SecurityReader
    "f023fd81-a637-4b56-95fd-791ac0226033",#	ServiceSupportAdministrator
    "f28a1f50-f6e7-4571-818b-6a12f2af6b6c",#	SharePointServiceAdministrator
    "baf37b3a-610e-45da-9e62-d9d1e5e8914b",#	TeamsCommunicationsAdministrator
    "f70938a0-fc10-4177-9e90-2178f8765737",#	TeamsCommunicationsSupportEngineer
    "fcf91098-03e3-41a9-b5ba-6f0ec8188a12",#	TeamsCommunicationsSupportSpecialist
    "3d762c5a-1b6c-493f-843e-55a3b42923d4",#	TeamsDevicesAdministrator
    "69091246-20e8-4a56-aa4d-066075b2a7a8",#	TeamsServiceAdministrator
    "fe930be7-5e62-47db-91af-98c3a49a38b1" #	UserAccountAdministrator

$RoleTemplateIds |
    Get-AADPIMRoleMember |
    # Where-Object synched |
    Out-GridView

The output contains a column / property telling you whether the account is synched or not. Follow up anyone that is synched, to make sure they use cloud managed accounts instead!

3 thoughts on “Finding synced accounts with Azure AD privileges

    1. It definitely helps, but there are several attack vectors present:

      – For federation, with the SolarWinds attacks, one has seen attackers forging tokens for accessing Azure AD. These forged tokens can contain the “MFA claim”, meaning that the token is accepted as MFA because Azure AD assumes the federation server has already conducted MFA.
      – For synced accounts without federation, it is a lot better, but the attacker might still “sneak in” coming from trusted sites, trusted compliant computers etc. exempted from MFA through CA etc.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s