Skip to content

Instantly share code, notes, and snippets.

@SamErde
Last active January 6, 2025 17:30
Show Gist options
  • Save SamErde/718242d2e8b801f4c26da093965e908f to your computer and use it in GitHub Desktop.
Save SamErde/718242d2e8b801f4c26da093965e908f to your computer and use it in GitHub Desktop.
This function retrieves all dynamic groups in Entra ID and checks if the membership rule contains any user-modifiable attributes. If it does, the group is considered unsafe.
function Get-UnsafeDynamicGroups {
<#
.SYNOPSIS
Get unsafe dynamic groups in Entra ID.
.DESCRIPTION
This function retrieves all dynamic groups in Entra ID and checks if the membership rule contains any user-modifiable attributes. If it does, the group is considered unsafe.
.EXAMPLE
Get-UnsafeDynamicGroups
.NOTES
Author: Sam Erde
Company: Sentinel Technologies, Inc.
Modified: 2025-01-01
Version: 0.1.0
To Do:
- Confirm list of user-modifiable attributes. [RED HERRING?]
- Add instructions for remediating or mitigating the unsafe group configuration.
- Check for dynamic groups with failed processing status or paused processing.
- Check for nested groups.
For now, this script is based on identifying dynamic membership rules that target user-modifiable attributes. The
script does not yet check for permissions on custom attributes. There are also other ways to create unsafe dynamic,
which I would like to check for in the future. Other examples that I would like to add in the future are described
in the blog posts referenced below. This script was inspired by blog posts about dynamic groups from Nairuz Abulhul
(@nairuzabulhul) and Cody Burkard (@codyburkard).
- <https://medium.com/r3d-buck3t/abusing-dynamic-groups-in-azuread-part-1-ff12e328c8c0>
- <https://mnemonic.no/blog/abusing-dynamic-groups-in-azure-ad-for-privilege-escalation/>
Potential General Checks:
- Check if the tenant allows users to invite guests.
- Check if any dynamic groups use any of these attributes in a membership rule (especially a partial match)
- mail, mailNickname, userPrincipalName, proxyAddresses, otherMails
Attributes of Potential Interest:
- Get groups where memberOf or TransitiveMemberOf has a value.
- Get groups where TransitiveMembers has a value.
- Get groups where ServiceProvisioningErrors has a value.
#>
# Requires -Version 5.1
[CmdletBinding()]
[OutputType([PSCustomObject[]])]
param ()
$RequiredModules = @(
'Microsoft.Graph.Authentication',
'Microsoft.Graph.Groups'
)
foreach ($module in $RequiredModules) {
if (-not (Get-Module -ListAvailable -Name $module)) {
try {
Install-Module -Name $module -Scope CurrentUser
} catch {
throw "Failed to install module: $module. Error: $_"
}
}
Import-Module -Name $module
}
# [ NEED TO CONFIRM ] List of built-in user-modifiable attributes that are considered unsafe.
$UserModifiableAttributes = ('displayName', 'givenName', 'surname', 'preferredLanguage', 'mobilePhone', 'businessPhones')
# Find one or more instances of a leading '(' that is followed by an "objectType.propertyName" string (e.g. 'user.surname').
[string]$RulePattern = '\((\w+\.\w+)\s-[a-zA-Z]+'
# Pre-create a list of objects to store the results.
[System.Collections.Generic.List[object]]$UnsafeDynamicGroups = @()
try {
Connect-MgGraph -Scopes 'User.Read.All', 'Group.Read.All' -NoWelcome
Get-MgContext | Select-Object TokenCredentialType, Account, AppName, ContextScope, TenantId | Write-Verbose
} catch {
throw "Failed to connect to Microsoft Graph. Error: $_"
}
# Get all dynamic groups and check if their membership rules contain any user-modifiable attributes.
[Microsoft.Graph.PowerShell.Models.MicrosoftGraphGroup[]]$DynamicGroups = Get-MgGroup -Filter "groupTypes/any(c:c eq 'DynamicMembership')" -All:$true -ConsistencyLevel eventual
Write-Verbose "Found $($DynamicGroups.Count) dynamic groups in this tenant."
foreach ($group in $DynamicGroups) {
Write-Verbose "+ Inspecting dynamic group: $($group.DisplayName)"
Remove-Variable UnsafeRuleProperty, matches -ErrorAction SilentlyContinue
[string[]]$UnsafeRuleProperty = @()
[string]$RuleString = $group.MembershipRule
Write-Verbose " + Inspecting dynamic group membership rule: $RuleString"
$RuleMatches = [regex]::matches($RuleString, $RulePattern)
[string[]]$RuleProperty = $RuleMatches | ForEach-Object { $_.Groups[1].Value }
foreach ($property in $RuleProperty) {
Write-Verbose " + Inspecting dynamic rule property: $($property)"
$ObjectProperty = $property.Split('.')[1]
if ($UserModifiableAttributes -contains $ObjectProperty) {
$UnsafeRuleProperty += $property
Write-Verbose " - Found unsafe dynamic rule property: ''$property'' in rule: ''$RuleString''."
} else {
Write-Verbose " - The property ''$property'' is not considered unsafe."
}
}
# Create a list of custom objects that contain the results.
if ($UnsafeRuleProperty.Count -gt 0) {
# Create custom object that contains the group DisplayName, the list of unsafe properties, and the group object.
$UnsafeDynamicGroups.Add( [PSCustomObject]@{
DisplayName = $group.DisplayName
UnsafeRuleProperty = $UnsafeRuleProperty
UnsafeRule = $RuleString
MembershipProcessingState = $group.MembershipRuleProcessingState
Group = $group
})
}
}
$UnsafeDynamicGroups
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment