Role-assignable group created


Description

Identifies creation of a new Entra ID role-assignable group, a rare and tightly-permissioned event that can be a precursor to privilege escalation via later membership and role assignment changes.

Query · kql

let timeframe = 14d;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| 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 GroupName = tostring(TargetResources[0].displayName)
| extend GroupId   = tostring(TargetResources[0].id)
| 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))
| extend AccountName      = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), Actor)
| extend AccountUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
| project
    TimeGenerated,
    GroupName,
    GroupId,
    Actor,
    AccountName,
    AccountUPNSuffix,
    ActorIp,
    CorrelationId
| sort by TimeGenerated desc
Raw source Role-assignable group created · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
id: 745c2179-a7db-4185-90c1-b33d78c5a6ac
name: Role-assignable group created
description: |
  Identifies creation of a new Entra ID role-assignable group, a rare and tightly-permissioned event that can be a precursor to privilege escalation via later membership and role assignment changes.
description-detailed: |
  Group role-assignability (isAssignableToRole set to true) can only be set
  when a group is created; Microsoft Entra ID does not allow converting an
  existing group into a role-assignable one afterward, and creating one
  requires the actor to hold at least the Privileged Role Administrator role.
  By design there is no legitimate high-volume use of this event in a
  well-run tenant, and a maximum of 500 role-assignable groups are permitted
  per tenant, so every match here is inherently rare and should map to a
  known change.
  An attacker who has obtained Privileged Role Administrator or Global
  Administrator access can create a role-assignable group with an innocuous
  display name, then add members or owners to it and later assign a directory
  role to the group. Because role-assignable groups exist specifically so that
  group membership grants a role, standard "Add member to role" monitoring
  never fires for the accounts that gain access this way; the group creation
  and its membership changes are the only audit signal. See the companion
  hunting query for member and owner additions shortly after creation.
  Analysts should confirm the creation aligns with a documented change and
  check which accounts currently hold the Privileged Role Administrator role.
  References:
  - https://learn.microsoft.com/entra/identity/role-based-access-control/groups-concept
  - https://attack.mitre.org/techniques/T1098/003/
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
tactics:
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1098.003
query: |
  let timeframe = 14d;
  AuditLogs
  | where TimeGenerated >= ago(timeframe)
  | 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 GroupName = tostring(TargetResources[0].displayName)
  | extend GroupId   = tostring(TargetResources[0].id)
  | 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))
  | extend AccountName      = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), Actor)
  | extend AccountUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
  | project
      TimeGenerated,
      GroupName,
      GroupId,
      Actor,
      AccountName,
      AccountUPNSuffix,
      ActorIp,
      CorrelationId
  | sort by TimeGenerated desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ActorIp
version: 1.0.0
metadata:
    source:
        kind: Community
    author:
        name: descambiado
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection", "Identity" ]

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.