Home Do you check for group owners of privileged roles?
Post
Cancel

Do you check for group owners of privileged roles?

Introduction

This is the second blog in the series. If you havent read the first one you can find it here. In the previous blogpost we focussed on retrieving userobjects from groups and roles and created a PowerShell cmdlet to get a overview of the users of 14 privileged roles. The created PowerShell cmdlet Get-AzureADPrivilegedRolesMembers searches recursivly through all these roles and return all the userobjects. This blog we will focus on group owners and enumerating them.

Group Owners

A group owner can add members to the group without being part of the group. This is important when searching for privileged users since the following scenario could exist in Azure tenants:

A privileged role has one membership, which is a group. This group has one member Bob, but the user Alice own’s this group. This owner can add members to this group, resulting in the assignment of the role to the user. In my tenant I configured the following: The Authentication Administrator role has one membership which is the group Administrator. The group has one member which is Groupuser and the owner of the group is GroupOwnerUser.

Get-AzureADUserMFAConfiguration

When we run the created PowerShell cmdlet Get-AzureADDirectoryRoleMemberRecursive to list all the members of the role Authenticated Administrators it returns the user Groupuser:

1
2
3
4
5
Get-AzureADDirectoryRole -ObjectId 594857e7-f7b4-4fd0-892b-6e1e66237b8f | Get-AzureADDirectoryRoleMemberRecursive

ObjectId                             DisplayName UserPrincipalName       UserType
--------                             ----------- -----------------       --------
eb815e66-31a5-45ca-bed8-2b0f5e24f62f GroupUser   GroupUser@jonyschats.nl Member

But it doesn’t take into account the owner of the group. When we query the memberships of the role we see that the Administrator group is a member and when we query the owner of the group we see that GroupOwnerUser is owner of the group:

1
2
3
4
5
6
7
8
9
10
11
12
Get-AzureADDirectoryRoleMember -ObjectId 594857e7-f7b4-4fd0-892b-6e1e66237b8f

ObjectId                             DisplayName   Description
--------                             -----------   -----------
58b18ca8-2b9d-49b0-acfa-d0a68299a580 Administrator


Get-AzureADGroupOwner -ObjectId 58b18ca8-2b9d-49b0-acfa-d0a68299a580

ObjectId                             DisplayName    UserPrincipalName            UserType
--------                             -----------    -----------------            --------
2cc999ae-fe8e-4ce9-a18a-309d68f5bce2 GroupOwnerUser GroupOwnerUser@jonyschats.nl Member

Changing the already created cmdlets

A group owner can add members to that group. This means that the user GroupOwnerUser can add members to the group Administrator, which will give the newly added user the role Authentication administrator. Group owners can add themself and should be considered as highly privileged users if they own a group which is a member of a high privileged role.

To retrieve group objects even if they are nested I changed the Get-AzureADGroupMemberRecursive function and added the -ReturnGroups parameter which will make the function return groups instead of users.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...snip...
Write-Verbose -Message "Enumerating $($AzureGroup.DisplayName)"
$Members = Get-AzureADGroupMember -ObjectId $AzureGroup.ObjectId -All $true

if ($ReturnGroups){
	$UserMembers = $Members | Where-Object{$_.ObjectType -eq 'Group'}
	$Output += $UserMembers
	
	$GroupMembers = $Members | Where-Object{$_.ObjectType -eq 'Group'}
	If($GroupMembers){
		$UserMembers = $GroupMembers | ForEach-Object{ Get-AzureADGroupMemberRecursive -ReturnGroups -AzureGroup $_}
		$Output += $UserMembers
	}
}
else {
	$UserMembers = $Members | Where-Object{$_.ObjectType -eq 'User'}
	$Output += $UserMembers
	
	$GroupMembers = $Members | Where-Object{$_.ObjectType -eq 'Group'}
	If($GroupMembers){
		$UserMembers = $GroupMembers | ForEach-Object{ Get-AzureADGroupMemberRecursive -AzureGroup $_}
		$Output += $UserMembers
	}
}
...snip...

The cmdlet now returns only group objects when the parameter is given, to be sure it searched recusivly I created a extra group NestedNestedgroup and placed it inside the group Nestedgroup. The membership structure is as follows:

  • Test Group
    • NestedGroup
      • NestedNestedgroup
1
2
3
4
5
6
Get-AzureADGroup -ObjectId f5108639-9aca-4694-864e-c4e00186706b | Get-AzureADGroupMemberRecursive -ReturnGroups

ObjectId                             DisplayName       Description
--------                             -----------       -----------
50c16bf1-5016-4dfe-8bcc-d273c3b02f02 NestedGroup
8510e6c0-fa05-49ca-ab5c-26d3d59a2e4d NestedNestedGroup

I also changed the Get-AzureADGroupMemberRecursive cmdlet and added the parameter -ReturnGroups. I made the same changes to Get-AzureADPrivilegedRolesMembers. When you run the cmdlet now with the parameter -ReturnGroups it will search recursivly through the 14 roles for group objects and return them:

1
2
3
4
5
Get-AzureADPrivilegedRolesMembers -ReturnGroups

ObjectId                             DisplayName   Description
--------                             -----------   -----------
58b18ca8-2b9d-49b0-acfa-d0a68299a580 Administrator

Then I tried adding a group to the Administrator group to create a nested group effect. But I figured out that is not possible since you can’t create nested groups if the group is elligible to be assigned to a role. Well okay, atleast my function is future proof if add that ability. Facepalm!

When we run Get-AzureADPrivilegedRolesMembers -ReturnGroups and pipe it to Get-AzureADGroupOwner. The following will happen:

  • loop through the 14 privileged roles
  • list all the group objects and return group objects
  • these group objects will be piped to retrieve the owners of all the groups
1
2
3
4
5
Get-AzureADPrivilegedRolesMembers -ReturnGroups | Get-AzureADGroupOwner

ObjectId                             DisplayName    UserPrincipalName            UserType
--------                             -----------    -----------------            --------
2cc999ae-fe8e-4ce9-a18a-309d68f5bce2 GroupOwnerUser GroupOwnerUser@jonyschats.nl Member

So even though the user GroupOwnerUser@jonyschats.nl doesn’t have a role that gives him high privileges, the user should be considered as a high privileged user since he owns a group which is member of a high privileged role.

Updating the overview cmdlet

To get a good overview again I changed the existing Get-AzureADPrivilegedRolesOverview cmdlet and added the groupcount, groups and groupowners attributes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Function Get-AzureADPrivilegedRolesOverview{
<#
.SYNOPSIS
Author: Jony Schats - 0xjs
Required Dependencies: Get-AzureADDirectoryRole, Get-AzureADDirectoryRoleMember, Get-AzureADGroupMember, Get-AzureADGroupMemberRecursive
Optional Dependencies: None

.DESCRIPTION
Recursively search through privileged Azure AD roles and return a overview of the amount of members a role has and the members itself.

.EXAMPLE
Get-AzureADPrivilegedRolesOverview

#>
    Begin{
		# Check if Azure AD is loaded
		If(-not(Get-Command *Get-AzureADCurrentSessionInfo*)){
			Write-Host -ForegroundColor Red "AzureAD Module not imported, stopping"
			break
		}
        
		# Check connection with AzureAD
		try {
			$var = Get-AzureADTenantDetail
		}
		catch {
			Write-Host -ForegroundColor Red "You're not connected with AzureAD, Connect with Connect-AzureAD"
			break
		}
		
		$AdminRoles = "Global administrator", "Application administrator", "Authentication Administrator", "Billing administrator", "Cloud application administrator", "Conditional Access administrator", "Exchange administrator", "Helpdesk administrator", "Password administrator", "Privileged authentication administrator", "Privileged Role Administrator", "Security administrator", "SharePoint administrator", "User administrator"
		$Output = @()
    }
	
	Process {		
		foreach ($AdminRole in $AdminRoles) {
			$AdminRoleData = Get-AzureADDirectoryRole | Where-Object -Property Displayname -eq $AdminRole
			Write-Verbose -Message "Enumerating $($AdminRoleData.DisplayName)"

			# If the role is populated
			if ($AdminRoleData -ne $null){
				
				# Retrieve members of the AdminRole
				$AdminRoleMembersUsers = Get-AzureADDirectoryRole -ObjectId $AdminRoleData.ObjectId | Get-AzureADDirectoryRoleMemberRecursive
				$AdminRoleMembersUsersCount = $AdminRoleMembersUsers | Sort-Object -Unique | Measure-Object
				
				$AdminRoleMembersGroups = Get-AzureADDirectoryRole -ObjectId $AdminRoleData.ObjectId | Get-AzureADDirectoryRoleMemberRecursive -ReturnGroups
				$AdminRoleMembersGroupsCount = $AdminRoleMembersGroups | Sort-Object -Unique | Measure-Object
				
				$GroupOwners = Get-AzureADDirectoryRole -ObjectId $AdminRoleData.ObjectId | Get-AzureADDirectoryRoleMemberRecursive -ReturnGroups | Get-AzureADGroupOwner
				
				$item = New-Object PSObject
				$item | Add-Member -type NoteProperty -Name 'Role' -Value $AdminRoleData.DisplayName
				$item | Add-Member -type NoteProperty -Name 'UserCount' -Value $AdminRoleMembersUsersCount.Count
				$item | Add-Member -type NoteProperty -Name 'Users' -Value $AdminRoleMembersUsers.UserPrincipalName
				$item | Add-Member -type NoteProperty -Name 'GroupCount' -Value $AdminRoleMembersGroupsCount.Count
				$item | Add-Member -type NoteProperty -Name 'Groups' -Value $AdminRoleMembersGroups.DisplayName
				$item | Add-Member -type NoteProperty -Name 'GroupOwners' -Value $GroupOwners.UserPrincipalName				
				$Output += $item
			}
			else {
				$item = New-Object PSObject
				$item | Add-Member -type NoteProperty -Name 'Role' -Value $AdminRole
				$item | Add-Member -type NoteProperty -Name 'UserCount' -Value "0"
				$item | Add-Member -type NoteProperty -Name 'GroupCount' -Value "0"
				$Output += $item
			}
		}
	}

	end {
        Return $Output | Sort-Object -Property UserCount -Descending
    }
}

The output of the new cmdlet looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Get-AzureADPrivilegedRolesOverview | ft

Role                                    UserCount Users                   GroupCount Groups        GroupOwners
----                                    --------- -----                   ---------- ------        -----------
Authentication Administrator                    1 GroupUser@jonyschats.nl          1 Administrator GroupOwnerUser@jonyschats.nl
Global Administrator                            1 0xjs@jonyschats.nl               0
Privileged Role Administrator                   0                                  0
Privileged authentication administrator         0                                  0
Password administrator                          0                                  0
User Administrator                              0                                  0
SharePoint administrator                        0                                  0
Security administrator                          0                                  0
Cloud application administrator                 0                                  0
Billing administrator                           0                                  0
Application administrator                       0                                  0
Helpdesk administrator                          0                                  0
Exchange administrator                          0                                  0
Conditional Access administrator                0                                  0

Quick recap

A quick recap of what I have learned about Azure AD identities:

  • Roles can be assigned these three identities and these identities:
    • Users
    • Groups
      • Can’t have nested groups when assigned to roles.
      • Can have owners
    • ServicePrincipals

In the next blog we will dive into ServicePrincipals and yes these can have owners too.

GitHub

All the cmdlets can be found in my GitHub project AzurePowerCommand.

This post is licensed under CC BY 4.0 by the author.
Contents