Back
Idd6cd3c6f-1d2a-4c42-a048-dbe91cf35b18
Rulename[Entra ID] Mass Privileged Role Change Activity Detected
DescriptionDetects multiple privileged role changes occurring around the same time. This may signal bulk abuse or administrative misconfiguration.
SeverityHigh
TacticsPrivilegeEscalation
TechniquesT1078
Required data connectorsAzureActiveDirectory
KindScheduled
Query frequency2h
Query period2h
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/eDCRule/Analytic%20Rules/%5BEntra%20ID%5D%20Mass%20Privileged%20Role%20Change%20Activity%20Detected.yaml
Version1.0.0
Arm templated6cd3c6f-1d2a-4c42-a048-dbe91cf35b18.json
Deploy To Azure
AuditLogs
| where Category =~ "RoleManagement"
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
| extend InitiatingAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName)
| where RoleName contains "Admin"
| extend Target = tostring(TargetResources.userPrincipalName)
| summarize dcount(Target) by bin(TimeGenerated, 1h)
| where dcount_Target >= 9
| join kind=rightsemi  (AuditLogs
    | where Category =~ "RoleManagement"
    | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
    | mv-expand TargetResources
    | mv-expand TargetResources.modifiedProperties
    | extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
    | where displayName_ =~ "Role.DisplayName"
    | extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
    | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
    | extend Source_UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
    | where RoleName contains "Admin"
    | extend Target = tostring(TargetResources.userPrincipalName)
    | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
    | extend Source_Network_IPLocation = ""
    | extend TimeWindow = bin(TimeGenerated, 1h))
    on $left.TimeGenerated == $right.TimeWindow
| project
        Alert_Category_en = "Entra ID",
    Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
    Alert_Name_en = "Mass Privileged Role Change Activity Detected",
    Alert_Description_en =  strcat(
                            "At Taiwan time: ",
                            format_datetime(datetime_utc_to_local(TimeGenerated, "Asia/Taipei"), "yyyy-MM-dd HH:mm:ss"),
                            "in the Microsoft Entra ID tenant"
    ", user:  ",
                            iff(isnotempty(Source_UserPrincipalName), Source_UserPrincipalName, InitiatingAppName),
                            " at IP: ",
                            InitiatingIpAddress,
                            ", performed mass permission changes. Newly added role permission: ",
                            RoleName
                        ),
    Alert_TriageStep_en=
    strcat(
    "1. Confirm whether the high-privilege role assignment  ",
    iff(isnotempty(RoleName), RoleName, "<NoRole>"),
    "  has a corresponding change request or approval record.",
    "2. Verify whether target account  ",
    iff(isnotempty(Target), Target, "<NoTargetUser>"),
    "  is a user who actually needs this administrative permission.",
    "3. Check whether source IP  ",
    iff(isnotempty(InitiatingIpAddress), InitiatingIpAddress, "<NoIP>"),
    "  and sign-in location  ",
    iff(isnotempty(Source_Network_IPLocation), Source_Network_IPLocation, "<NoLocation>"),
    "  are company IPs and commonly used locations."
),
    Alert_Containment_en = strcat(
                           "1. Immediately review and suspend all Admin role members added during the event time window, confirm whether they are approved permission changes, and immediately remove unauthorized roles.",
                           "2. Immediately restrict or disable the source account or application that performed the role assignment (",
                           iff(isnotempty(InitiatingAppName), InitiatingAppName, "<NoInitiator>"),
                           "), and revoke its existing sign-in tokens and sessions. ",
                           "3. If the source is a Service Principal, immediately disable the application, revoke credentials/secrets and Graph API permissions, and prevent continued automation abuse. "
                       ),
    Alert_Remediation_en = strcat(
                           "1. Strengthen Admin role assignment governance: allow Just-In-Time access only through PIM, require approval and access justification, and prohibit permanent assignments. ",
                           "2. Establish real-time alerts and automated response (SOAR) for role changes, including mass assignments, off-hours activity, or abnormal geolocation behavior.",
                           "3. Regularly audit Admin role members/Service Principal permissions and API consent, and remove unnecessary or long-unused high-privilege access. "
                       ),
            Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
    Alert_Time_UTC0 = TimeGenerated,
    Event_Action = ActivityDisplayName,
    Event_Status = Result,
    //Event_Description = ActivityDisplayName,
    Source_Identity_FullName = iff(isnotempty(Source_UserPrincipalName), Source_UserPrincipalName, InitiatingAppName),
    //Source_Identity_ID = ActorID,
    Source_Identity_Type = iff(isnotempty(Source_UserPrincipalName), "User", "Service"),
    Source_Network_IPAddress = InitiatingIpAddress,
    Source_Network_IPLocation = Source_Network_IPLocation,
    //Source_Resource_Name = InitiatingAppName,
    Target_Identity_FullName = Target,
    Target_Identity_DomainType = iff(Target contains "EXT", 'External', 'Internal'),
    Target_Identity_Type = "User",
    Target_Resource_ID = "",
    Target_Resource_Type = "Entra ID"
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: Source_Identity_FullName
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: Source_Network_IPAddress
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: Target_Identity_FullName
name: '[Entra ID] Mass Privileged Role Change Activity Detected'
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/eDCRule/Analytic%20Rules/%5BEntra%20ID%5D%20Mass%20Privileged%20Role%20Change%20Activity%20Detected.yaml
queryFrequency: 2h
triggerOperator: gt
status: Available
queryPeriod: 2h
requiredDataConnectors:
- connectorId: AzureActiveDirectory
  dataTypes:
  - AuditLogs
tactics:
- PrivilegeEscalation
query: |
  AuditLogs
  | where Category =~ "RoleManagement"
  | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
  | mv-expand TargetResources
  | mv-expand TargetResources.modifiedProperties
  | extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
  | extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
  | extend InitiatingAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName)
  | where RoleName contains "Admin"
  | extend Target = tostring(TargetResources.userPrincipalName)
  | summarize dcount(Target) by bin(TimeGenerated, 1h)
  | where dcount_Target >= 9
  | join kind=rightsemi  (AuditLogs
      | where Category =~ "RoleManagement"
      | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
      | mv-expand TargetResources
      | mv-expand TargetResources.modifiedProperties
      | extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
      | where displayName_ =~ "Role.DisplayName"
      | extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
      | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
      | extend Source_UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
      | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
      | where RoleName contains "Admin"
      | extend Target = tostring(TargetResources.userPrincipalName)
      | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
      | extend Source_Network_IPLocation = ""
      | extend TimeWindow = bin(TimeGenerated, 1h))
      on $left.TimeGenerated == $right.TimeWindow
  | project
          Alert_Category_en = "Entra ID",
      Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
      Alert_Name_en = "Mass Privileged Role Change Activity Detected",
      Alert_Description_en =  strcat(
                              "At Taiwan time: ",
                              format_datetime(datetime_utc_to_local(TimeGenerated, "Asia/Taipei"), "yyyy-MM-dd HH:mm:ss"),
                              "in the Microsoft Entra ID tenant"
      ", user:  ",
                              iff(isnotempty(Source_UserPrincipalName), Source_UserPrincipalName, InitiatingAppName),
                              " at IP: ",
                              InitiatingIpAddress,
                              ", performed mass permission changes. Newly added role permission: ",
                              RoleName
                          ),
      Alert_TriageStep_en=
      strcat(
      "1. Confirm whether the high-privilege role assignment  ",
      iff(isnotempty(RoleName), RoleName, "<NoRole>"),
      "  has a corresponding change request or approval record.",
      "2. Verify whether target account  ",
      iff(isnotempty(Target), Target, "<NoTargetUser>"),
      "  is a user who actually needs this administrative permission.",
      "3. Check whether source IP  ",
      iff(isnotempty(InitiatingIpAddress), InitiatingIpAddress, "<NoIP>"),
      "  and sign-in location  ",
      iff(isnotempty(Source_Network_IPLocation), Source_Network_IPLocation, "<NoLocation>"),
      "  are company IPs and commonly used locations."
  ),
      Alert_Containment_en = strcat(
                             "1. Immediately review and suspend all Admin role members added during the event time window, confirm whether they are approved permission changes, and immediately remove unauthorized roles.",
                             "2. Immediately restrict or disable the source account or application that performed the role assignment (",
                             iff(isnotempty(InitiatingAppName), InitiatingAppName, "<NoInitiator>"),
                             "), and revoke its existing sign-in tokens and sessions. ",
                             "3. If the source is a Service Principal, immediately disable the application, revoke credentials/secrets and Graph API permissions, and prevent continued automation abuse. "
                         ),
      Alert_Remediation_en = strcat(
                             "1. Strengthen Admin role assignment governance: allow Just-In-Time access only through PIM, require approval and access justification, and prohibit permanent assignments. ",
                             "2. Establish real-time alerts and automated response (SOAR) for role changes, including mass assignments, off-hours activity, or abnormal geolocation behavior.",
                             "3. Regularly audit Admin role members/Service Principal permissions and API consent, and remove unnecessary or long-unused high-privilege access. "
                         ),
              Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
      Alert_Time_UTC0 = TimeGenerated,
      Event_Action = ActivityDisplayName,
      Event_Status = Result,
      //Event_Description = ActivityDisplayName,
      Source_Identity_FullName = iff(isnotempty(Source_UserPrincipalName), Source_UserPrincipalName, InitiatingAppName),
      //Source_Identity_ID = ActorID,
      Source_Identity_Type = iff(isnotempty(Source_UserPrincipalName), "User", "Service"),
      Source_Network_IPAddress = InitiatingIpAddress,
      Source_Network_IPLocation = Source_Network_IPLocation,
      //Source_Resource_Name = InitiatingAppName,
      Target_Identity_FullName = Target,
      Target_Identity_DomainType = iff(Target contains "EXT", 'External', 'Internal'),
      Target_Identity_Type = "User",
      Target_Resource_ID = "",
      Target_Resource_Type = "Entra ID"
description: |
  Detects multiple privileged role changes occurring around the same time. This may signal bulk abuse or administrative misconfiguration.
severity: High
kind: Scheduled
triggerThreshold: 0
id: d6cd3c6f-1d2a-4c42-a048-dbe91cf35b18
alertDetailsOverride:
  alertDynamicProperties: []
version: 1.0.0
relevantTechniques:
- T1078
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2024-01-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/d6cd3c6f-1d2a-4c42-a048-dbe91cf35b18')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/d6cd3c6f-1d2a-4c42-a048-dbe91cf35b18')]",
      "properties": {
        "alertDetailsOverride": {
          "alertDynamicProperties": []
        },
        "alertRuleTemplateName": "d6cd3c6f-1d2a-4c42-a048-dbe91cf35b18",
        "customDetails": null,
        "description": "Detects multiple privileged role changes occurring around the same time. This may signal bulk abuse or administrative misconfiguration.\n",
        "displayName": "[Entra ID] Mass Privileged Role Change Activity Detected",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "Source_Identity_FullName",
                "identifier": "FullName"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "Source_Network_IPAddress",
                "identifier": "Address"
              }
            ]
          },
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "Target_Identity_FullName",
                "identifier": "FullName"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/eDCRule/Analytic%20Rules/%5BEntra%20ID%5D%20Mass%20Privileged%20Role%20Change%20Activity%20Detected.yaml",
        "query": "AuditLogs\n| where Category =~ \"RoleManagement\"\n| where ActivityDisplayName has_any (\"Add eligible member to role\", \"Add member to role\")\n| mv-expand TargetResources\n| mv-expand TargetResources.modifiedProperties\n| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)\n| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))\n| extend InitiatingAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName)\n| where RoleName contains \"Admin\"\n| extend Target = tostring(TargetResources.userPrincipalName)\n| summarize dcount(Target) by bin(TimeGenerated, 1h)\n| where dcount_Target >= 9\n| join kind=rightsemi  (AuditLogs\n    | where Category =~ \"RoleManagement\"\n    | where ActivityDisplayName has_any (\"Add eligible member to role\", \"Add member to role\")\n    | mv-expand TargetResources\n    | mv-expand TargetResources.modifiedProperties\n    | extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)\n    | where displayName_ =~ \"Role.DisplayName\"\n    | extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))\n    | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)\n    | extend Source_UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)\n    | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)\n    | where RoleName contains \"Admin\"\n    | extend Target = tostring(TargetResources.userPrincipalName)\n    | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))\n    | extend Source_Network_IPLocation = \"\"\n    | extend TimeWindow = bin(TimeGenerated, 1h))\n    on $left.TimeGenerated == $right.TimeWindow\n| project\n        Alert_Category_en = \"Entra ID\",\n    Alert_SubCategory_en = \"Anomaly Identity Privilege Modification\",\n    Alert_Name_en = \"Mass Privileged Role Change Activity Detected\",\n    Alert_Description_en =  strcat(\n                            \"At Taiwan time: \",\n                            format_datetime(datetime_utc_to_local(TimeGenerated, \"Asia/Taipei\"), \"yyyy-MM-dd HH:mm:ss\"),\n                            \"in the Microsoft Entra ID tenant\"\n    \", user:  \",\n                            iff(isnotempty(Source_UserPrincipalName), Source_UserPrincipalName, InitiatingAppName),\n                            \" at IP: \",\n                            InitiatingIpAddress,\n                            \", performed mass permission changes. Newly added role permission: \",\n                            RoleName\n                        ),\n    Alert_TriageStep_en=\n    strcat(\n    \"1. Confirm whether the high-privilege role assignment  \",\n    iff(isnotempty(RoleName), RoleName, \"<NoRole>\"),\n    \"  has a corresponding change request or approval record.\",\n    \"2. Verify whether target account  \",\n    iff(isnotempty(Target), Target, \"<NoTargetUser>\"),\n    \"  is a user who actually needs this administrative permission.\",\n    \"3. Check whether source IP  \",\n    iff(isnotempty(InitiatingIpAddress), InitiatingIpAddress, \"<NoIP>\"),\n    \"  and sign-in location  \",\n    iff(isnotempty(Source_Network_IPLocation), Source_Network_IPLocation, \"<NoLocation>\"),\n    \"  are company IPs and commonly used locations.\"\n),\n    Alert_Containment_en = strcat(\n                           \"1. Immediately review and suspend all Admin role members added during the event time window, confirm whether they are approved permission changes, and immediately remove unauthorized roles.\",\n                           \"2. Immediately restrict or disable the source account or application that performed the role assignment (\",\n                           iff(isnotempty(InitiatingAppName), InitiatingAppName, \"<NoInitiator>\"),\n                           \"), and revoke its existing sign-in tokens and sessions. \",\n                           \"3. If the source is a Service Principal, immediately disable the application, revoke credentials/secrets and Graph API permissions, and prevent continued automation abuse. \"\n                       ),\n    Alert_Remediation_en = strcat(\n                           \"1. Strengthen Admin role assignment governance: allow Just-In-Time access only through PIM, require approval and access justification, and prohibit permanent assignments. \",\n                           \"2. Establish real-time alerts and automated response (SOAR) for role changes, including mass assignments, off-hours activity, or abnormal geolocation behavior.\",\n                           \"3. Regularly audit Admin role members/Service Principal permissions and API consent, and remove unnecessary or long-unused high-privilege access. \"\n                       ),\n            Alert_Time_TW = datetime_utc_to_local(TimeGenerated, \"Asia/Taipei\"),\n    Alert_Time_UTC0 = TimeGenerated,\n    Event_Action = ActivityDisplayName,\n    Event_Status = Result,\n    //Event_Description = ActivityDisplayName,\n    Source_Identity_FullName = iff(isnotempty(Source_UserPrincipalName), Source_UserPrincipalName, InitiatingAppName),\n    //Source_Identity_ID = ActorID,\n    Source_Identity_Type = iff(isnotempty(Source_UserPrincipalName), \"User\", \"Service\"),\n    Source_Network_IPAddress = InitiatingIpAddress,\n    Source_Network_IPLocation = Source_Network_IPLocation,\n    //Source_Resource_Name = InitiatingAppName,\n    Target_Identity_FullName = Target,\n    Target_Identity_DomainType = iff(Target contains \"EXT\", 'External', 'Internal'),\n    Target_Identity_Type = \"User\",\n    Target_Resource_ID = \"\",\n    Target_Resource_Type = \"Entra ID\"\n",
        "queryFrequency": "PT2H",
        "queryPeriod": "PT2H",
        "severity": "High",
        "status": "Available",
        "subTechniques": [],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "PrivilegeEscalation"
        ],
        "techniques": [
          "T1078"
        ],
        "templateVersion": "1.0.0",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}