Member or owner added to a role-assignable group within 24 hours of its creation
Description
Identifies members or owners added to a role-assignable group within 24 hours of its creation, a pattern used to grant privileged access without generating a direct role-assignment audit event.
Query · kql
let timeframe = 1d;
let creationLookback = 14d;
let window = 24h;
let RecentlyCreatedRoleAssignableGroups =
AuditLogs
| where TimeGenerated >= ago(timeframe + creationLookback) and TimeGenerated < ago(0h)
| where Category =~ "GroupManagement"
| where OperationName =~ "Add group"
| where Result =~ "success"
| mv-expand ModProp = TargetResources[0].modifiedProperties
| extend PropName = tostring(ModProp.displayName)
| extend NewValue = tostring(ModProp.newValue)
| where PropName has "IsAssignableToRole" or NewValue has "isAssignableToRole"
| where NewValue has "true"
| extend GroupId = tolower(tostring(TargetResources[0].id))
| where isnotempty(GroupId)
| project GroupId, CreationTime = TimeGenerated;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where Category =~ "GroupManagement"
| where OperationName in~ ("Add member to group", "Add owner to group")
| where Result =~ "success"
| extend ActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorApp = tostring(InitiatedBy.app.displayName)
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| extend ActorIp = iff(
isnotempty(tostring(InitiatedBy.user.ipAddress)),
tostring(InitiatedBy.user.ipAddress),
tostring(InitiatedBy.app.ipAddress))
| mv-apply TargetResource = TargetResources on (
where TargetResource.type =~ "User"
| extend AddedUpn = tostring(TargetResource.userPrincipalName),
AddedId = tostring(TargetResource.id),
Properties = TargetResource.modifiedProperties
)
| mv-apply Property = Properties on (
where Property.displayName =~ "Group.ObjectID"
| extend GroupId = tolower(trim('"', tostring(Property.newValue)))
)
| mv-apply Property = Properties on (
where Property.displayName =~ "Group.DisplayName"
| extend GroupName = trim('"', tostring(Property.newValue))
)
| where isnotempty(GroupId)
| join kind=inner RecentlyCreatedRoleAssignableGroups on GroupId
| where TimeGenerated >= CreationTime and TimeGenerated <= CreationTime + window
| extend AccountName = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), Actor)
| extend AccountUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
| project
TimeGenerated,
OperationName,
GroupName,
GroupId,
CreationTime,
AddedUpn,
AddedId,
Actor,
AccountName,
AccountUPNSuffix,
ActorIp,
CorrelationId
| sort by TimeGenerated desc