Back
Idb64ac0e2-a241-4b9a-ad3e-ae572630b295
Rulename[Entra ID] Privileged Role Assigned to a New User
DescriptionDetects when a privileged role is assigned to a new user account. This can indicate unauthorized elevation of privileges.
SeverityHigh
TacticsPersistence
PrivilegeEscalation
DefenseEvasion
InitialAccess
TechniquesT1078.004
Required data connectorsAzureActiveDirectory
KindScheduled
Query frequency1h
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/eDCRule/Analytic%20Rules/%5BEntra%20ID%5D%20Privileged%20Role%20Assigned%20to%20a%20New%20User.yaml
Version1.0.0
Arm templateb64ac0e2-a241-4b9a-ad3e-ae572630b295.json
Deploy To Azure
// Define the start and end times based on input values
let starttime = now() - 1h;
let endtime = now();
// Set a lookback period of 14 days
let lookback = starttime - 14d;
// Define a reusable function to query audit logs
let awsFunc = (start: datetime, end: datetime) {
    AuditLogs
    | where TimeGenerated between (start .. end)
    | where Category =~ "RoleManagement"
    | where AADOperationType in ("Assign", "AssignEligibleRole")
    | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
    | mv-apply TargetResource = TargetResources on
        (
        where TargetResource.type in~ ("User", "ServicePrincipal")
        | extend
            Target = iff(TargetResource.type =~ "ServicePrincipal", tostring(TargetResource.displayName), tostring(TargetResource.userPrincipalName)),
            props = TargetResource.modifiedProperties
        )
    | mv-apply Property = props on
        (
        where Property.displayName =~ "Role.DisplayName"
        | extend RoleName = trim('"', tostring(Property.newValue))
        )
    | where RoleName contains "Admin" and Result == "success"
};
// Query for audit events in the current day
let EventInfo_CurrentDay = awsFunc(starttime, endtime);
// Query for audit events in the historical period (lookback)
let EventInfo_historical = awsFunc(lookback, starttime);
// Find unseen events by performing a left anti-join
let EventInfo_Unseen = (EventInfo_CurrentDay
    | join kind=leftanti(EventInfo_historical) on Target, RoleName, OperationName
    );
// Extend and clean up the results
EventInfo_Unseen
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
// You can uncomment the lines below to filter out PIM activations
| where Initiator != "MS-PIM"
//| summarize StartTime=min(TimeGenerated), EndTime=min(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result
// Project specific columns and split them for further analysis
| project
    TimeGenerated,
    OperationName,
    RoleName,
    Target,
    Initiator,
    InitiatingUserPrincipalName,
    InitiatingAadUserId,
    InitiatingAppName,
    InitiatingAppServicePrincipalId,
    InitiatingIpAddress,
    Result
| extend
    TargetName = tostring(split(Target, '@', 0)[0]),
    TargetUPNSuffix = tostring(split(Target, '@', 1)[0]),
    InitiatorName = tostring(split(InitiatingUserPrincipalName, '@', 0)[0]),
    InitiatorUPNSuffix = tostring(split(InitiatingUserPrincipalName, '@', 1)[0])
| extend
    Source_Network_IPLocation = "",
    ActivityType=OperationName
| project
        Alert_Category_en = "Entra ID",
    Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
    Alert_Name_en = "Privileged Role Assigned to a New User",
    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",
                           "operator: ",
                           iff(isnotempty(Initiator), Initiator, InitiatingAppName),
                           ", from IP: ",
                           InitiatingIpAddress,
                           ", assigned privileged role  ",
                           tostring(RoleName),
                           "  to  ",
                           iff(isnotempty(Target), Target, "<NoTargetUser>"),
                           ". This permission change behavior has not appeared for this user recently (within 14 days)."
                       ),
    Alert_TriageStep_en = strcat(
                          "1. Confirm whether the privileged role assignment was performed through an Entra ID PIM-approved change process.",
                          "2. Confirm with the operator: ",
                          iff(isnotempty(Initiator), Initiator, "<UnknownInitiator>"),
                          "  whether this was performed by the account owner.",
                          "3. Check whether the source IP and geolocation are abnormal or not a company public IP."
                      ),
    Alert_Containment_en = strcat(
                           "1. If determined to be an unauthorized role assignment, immediately revoke the target  ",
                           iff(isnotempty(Target), Target, "<NoTargetUser>"),
                           " 's privileged role (",
                           tostring(RoleName),
                           "), and restore the state to before the change.",
                           "2. Immediately restrict the source identity (user or application) that performed the role assignment, revoke sign-in tokens, and force password and MFA reset if necessary.  ",
                           "3. If the source is an application (",
                           iff(isnotempty(InitiatingAppName), InitiatingAppName, "<NoServicePrincipal>"),
                           "), immediately disable the Service Principal and revoke high-privilege API consent and credentials/secrets.  "
                       ),
    Alert_Remediation_en = strcat(
                           "1. Allow privileged role access only through Entra ID PIM.",
                           "2. Enforce Conditional Access and MFA for role management and PIM operations, allowing them only from managed devices and named locations.",
                           "3. Establish real-time alerts and automated response (SOAR) for privileged role changes to detect abnormal role assignments immediately. "
                       ),
            Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
    Alert_Time_UTC0 = TimeGenerated,
    Event_Action = ActivityType,
    Event_Status = Result,
    //Event_Description = ActivityDisplayName,
    Source_Identity_FullName = iff(isnotempty(Initiator), Initiator, InitiatingAppName),
    //Source_Identity_ID = ActorID,
    Source_Identity_Type = iff(isnotempty(Initiator), "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:
  - columnName: Source_Identity_FullName
    identifier: FullName
- entityType: Account
  fieldMappings:
  - columnName: Target_Identity_FullName
    identifier: FullName
- entityType: IP
  fieldMappings:
  - columnName: Source_Network_IPAddress
    identifier: Address
query: |
  // Define the start and end times based on input values
  let starttime = now() - 1h;
  let endtime = now();
  // Set a lookback period of 14 days
  let lookback = starttime - 14d;
  // Define a reusable function to query audit logs
  let awsFunc = (start: datetime, end: datetime) {
      AuditLogs
      | where TimeGenerated between (start .. end)
      | where Category =~ "RoleManagement"
      | where AADOperationType in ("Assign", "AssignEligibleRole")
      | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
      | mv-apply TargetResource = TargetResources on
          (
          where TargetResource.type in~ ("User", "ServicePrincipal")
          | extend
              Target = iff(TargetResource.type =~ "ServicePrincipal", tostring(TargetResource.displayName), tostring(TargetResource.userPrincipalName)),
              props = TargetResource.modifiedProperties
          )
      | mv-apply Property = props on
          (
          where Property.displayName =~ "Role.DisplayName"
          | extend RoleName = trim('"', tostring(Property.newValue))
          )
      | where RoleName contains "Admin" and Result == "success"
  };
  // Query for audit events in the current day
  let EventInfo_CurrentDay = awsFunc(starttime, endtime);
  // Query for audit events in the historical period (lookback)
  let EventInfo_historical = awsFunc(lookback, starttime);
  // Find unseen events by performing a left anti-join
  let EventInfo_Unseen = (EventInfo_CurrentDay
      | join kind=leftanti(EventInfo_historical) on Target, RoleName, OperationName
      );
  // Extend and clean up the results
  EventInfo_Unseen
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  | extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
  // You can uncomment the lines below to filter out PIM activations
  | where Initiator != "MS-PIM"
  //| summarize StartTime=min(TimeGenerated), EndTime=min(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result
  // Project specific columns and split them for further analysis
  | project
      TimeGenerated,
      OperationName,
      RoleName,
      Target,
      Initiator,
      InitiatingUserPrincipalName,
      InitiatingAadUserId,
      InitiatingAppName,
      InitiatingAppServicePrincipalId,
      InitiatingIpAddress,
      Result
  | extend
      TargetName = tostring(split(Target, '@', 0)[0]),
      TargetUPNSuffix = tostring(split(Target, '@', 1)[0]),
      InitiatorName = tostring(split(InitiatingUserPrincipalName, '@', 0)[0]),
      InitiatorUPNSuffix = tostring(split(InitiatingUserPrincipalName, '@', 1)[0])
  | extend
      Source_Network_IPLocation = "",
      ActivityType=OperationName
  | project
          Alert_Category_en = "Entra ID",
      Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
      Alert_Name_en = "Privileged Role Assigned to a New User",
      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",
                             "operator: ",
                             iff(isnotempty(Initiator), Initiator, InitiatingAppName),
                             ", from IP: ",
                             InitiatingIpAddress,
                             ", assigned privileged role  ",
                             tostring(RoleName),
                             "  to  ",
                             iff(isnotempty(Target), Target, "<NoTargetUser>"),
                             ". This permission change behavior has not appeared for this user recently (within 14 days)."
                         ),
      Alert_TriageStep_en = strcat(
                            "1. Confirm whether the privileged role assignment was performed through an Entra ID PIM-approved change process.",
                            "2. Confirm with the operator: ",
                            iff(isnotempty(Initiator), Initiator, "<UnknownInitiator>"),
                            "  whether this was performed by the account owner.",
                            "3. Check whether the source IP and geolocation are abnormal or not a company public IP."
                        ),
      Alert_Containment_en = strcat(
                             "1. If determined to be an unauthorized role assignment, immediately revoke the target  ",
                             iff(isnotempty(Target), Target, "<NoTargetUser>"),
                             " 's privileged role (",
                             tostring(RoleName),
                             "), and restore the state to before the change.",
                             "2. Immediately restrict the source identity (user or application) that performed the role assignment, revoke sign-in tokens, and force password and MFA reset if necessary.  ",
                             "3. If the source is an application (",
                             iff(isnotempty(InitiatingAppName), InitiatingAppName, "<NoServicePrincipal>"),
                             "), immediately disable the Service Principal and revoke high-privilege API consent and credentials/secrets.  "
                         ),
      Alert_Remediation_en = strcat(
                             "1. Allow privileged role access only through Entra ID PIM.",
                             "2. Enforce Conditional Access and MFA for role management and PIM operations, allowing them only from managed devices and named locations.",
                             "3. Establish real-time alerts and automated response (SOAR) for privileged role changes to detect abnormal role assignments immediately. "
                         ),
              Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
      Alert_Time_UTC0 = TimeGenerated,
      Event_Action = ActivityType,
      Event_Status = Result,
      //Event_Description = ActivityDisplayName,
      Source_Identity_FullName = iff(isnotempty(Initiator), Initiator, InitiatingAppName),
      //Source_Identity_ID = ActorID,
      Source_Identity_Type = iff(isnotempty(Initiator), "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"
id: b64ac0e2-a241-4b9a-ad3e-ae572630b295
queryFrequency: 1h
status: Available
severity: High
relevantTechniques:
- T1078.004
version: 1.0.0
kind: Scheduled
tactics:
- Persistence
- PrivilegeEscalation
- DefenseEvasion
- InitialAccess
requiredDataConnectors:
- dataTypes:
  - AuditLogs
  connectorId: AzureActiveDirectory
description: |
  Detects when a privileged role is assigned to a new user account. This can indicate unauthorized elevation of privileges.
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/eDCRule/Analytic%20Rules/%5BEntra%20ID%5D%20Privileged%20Role%20Assigned%20to%20a%20New%20User.yaml
triggerOperator: gt
name: '[Entra ID] Privileged Role Assigned to a New User'
triggerThreshold: 0
queryPeriod: 14d
{
  "$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/b64ac0e2-a241-4b9a-ad3e-ae572630b295')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/b64ac0e2-a241-4b9a-ad3e-ae572630b295')]",
      "properties": {
        "alertRuleTemplateName": "b64ac0e2-a241-4b9a-ad3e-ae572630b295",
        "customDetails": null,
        "description": "Detects when a privileged role is assigned to a new user account. This can indicate unauthorized elevation of privileges.\n",
        "displayName": "[Entra ID] Privileged Role Assigned to a New User",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "Source_Identity_FullName",
                "identifier": "FullName"
              }
            ]
          },
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "Target_Identity_FullName",
                "identifier": "FullName"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "Source_Network_IPAddress",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/eDCRule/Analytic%20Rules/%5BEntra%20ID%5D%20Privileged%20Role%20Assigned%20to%20a%20New%20User.yaml",
        "query": "// Define the start and end times based on input values\nlet starttime = now() - 1h;\nlet endtime = now();\n// Set a lookback period of 14 days\nlet lookback = starttime - 14d;\n// Define a reusable function to query audit logs\nlet awsFunc = (start: datetime, end: datetime) {\n    AuditLogs\n    | where TimeGenerated between (start .. end)\n    | where Category =~ \"RoleManagement\"\n    | where AADOperationType in (\"Assign\", \"AssignEligibleRole\")\n    | where ActivityDisplayName has_any (\"Add eligible member to role\", \"Add member to role\")\n    | mv-apply TargetResource = TargetResources on\n        (\n        where TargetResource.type in~ (\"User\", \"ServicePrincipal\")\n        | extend\n            Target = iff(TargetResource.type =~ \"ServicePrincipal\", tostring(TargetResource.displayName), tostring(TargetResource.userPrincipalName)),\n            props = TargetResource.modifiedProperties\n        )\n    | mv-apply Property = props on\n        (\n        where Property.displayName =~ \"Role.DisplayName\"\n        | extend RoleName = trim('\"', tostring(Property.newValue))\n        )\n    | where RoleName contains \"Admin\" and Result == \"success\"\n};\n// Query for audit events in the current day\nlet EventInfo_CurrentDay = awsFunc(starttime, endtime);\n// Query for audit events in the historical period (lookback)\nlet EventInfo_historical = awsFunc(lookback, starttime);\n// Find unseen events by performing a left anti-join\nlet EventInfo_Unseen = (EventInfo_CurrentDay\n    | join kind=leftanti(EventInfo_historical) on Target, RoleName, OperationName\n    );\n// Extend and clean up the results\nEventInfo_Unseen\n| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)\n| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)\n| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)\n| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)\n| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))\n| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)\n// You can uncomment the lines below to filter out PIM activations\n| where Initiator != \"MS-PIM\"\n//| summarize StartTime=min(TimeGenerated), EndTime=min(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result\n// Project specific columns and split them for further analysis\n| project\n    TimeGenerated,\n    OperationName,\n    RoleName,\n    Target,\n    Initiator,\n    InitiatingUserPrincipalName,\n    InitiatingAadUserId,\n    InitiatingAppName,\n    InitiatingAppServicePrincipalId,\n    InitiatingIpAddress,\n    Result\n| extend\n    TargetName = tostring(split(Target, '@', 0)[0]),\n    TargetUPNSuffix = tostring(split(Target, '@', 1)[0]),\n    InitiatorName = tostring(split(InitiatingUserPrincipalName, '@', 0)[0]),\n    InitiatorUPNSuffix = tostring(split(InitiatingUserPrincipalName, '@', 1)[0])\n| extend\n    Source_Network_IPLocation = \"\",\n    ActivityType=OperationName\n| project\n        Alert_Category_en = \"Entra ID\",\n    Alert_SubCategory_en = \"Anomaly Identity Privilege Modification\",\n    Alert_Name_en = \"Privileged Role Assigned to a New User\",\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                           \"operator: \",\n                           iff(isnotempty(Initiator), Initiator, InitiatingAppName),\n                           \", from IP: \",\n                           InitiatingIpAddress,\n                           \", assigned privileged role  \",\n                           tostring(RoleName),\n                           \"  to  \",\n                           iff(isnotempty(Target), Target, \"<NoTargetUser>\"),\n                           \". This permission change behavior has not appeared for this user recently (within 14 days).\"\n                       ),\n    Alert_TriageStep_en = strcat(\n                          \"1. Confirm whether the privileged role assignment was performed through an Entra ID PIM-approved change process.\",\n                          \"2. Confirm with the operator: \",\n                          iff(isnotempty(Initiator), Initiator, \"<UnknownInitiator>\"),\n                          \"  whether this was performed by the account owner.\",\n                          \"3. Check whether the source IP and geolocation are abnormal or not a company public IP.\"\n                      ),\n    Alert_Containment_en = strcat(\n                           \"1. If determined to be an unauthorized role assignment, immediately revoke the target  \",\n                           iff(isnotempty(Target), Target, \"<NoTargetUser>\"),\n                           \" 's privileged role (\",\n                           tostring(RoleName),\n                           \"), and restore the state to before the change.\",\n                           \"2. Immediately restrict the source identity (user or application) that performed the role assignment, revoke sign-in tokens, and force password and MFA reset if necessary.  \",\n                           \"3. If the source is an application (\",\n                           iff(isnotempty(InitiatingAppName), InitiatingAppName, \"<NoServicePrincipal>\"),\n                           \"), immediately disable the Service Principal and revoke high-privilege API consent and credentials/secrets.  \"\n                       ),\n    Alert_Remediation_en = strcat(\n                           \"1. Allow privileged role access only through Entra ID PIM.\",\n                           \"2. Enforce Conditional Access and MFA for role management and PIM operations, allowing them only from managed devices and named locations.\",\n                           \"3. Establish real-time alerts and automated response (SOAR) for privileged role changes to detect abnormal role assignments immediately. \"\n                       ),\n            Alert_Time_TW = datetime_utc_to_local(TimeGenerated, \"Asia/Taipei\"),\n    Alert_Time_UTC0 = TimeGenerated,\n    Event_Action = ActivityType,\n    Event_Status = Result,\n    //Event_Description = ActivityDisplayName,\n    Source_Identity_FullName = iff(isnotempty(Initiator), Initiator, InitiatingAppName),\n    //Source_Identity_ID = ActorID,\n    Source_Identity_Type = iff(isnotempty(Initiator), \"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": "PT1H",
        "queryPeriod": "P14D",
        "severity": "High",
        "status": "Available",
        "subTechniques": [
          "T1078.004"
        ],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "DefenseEvasion",
          "InitialAccess",
          "Persistence",
          "PrivilegeEscalation"
        ],
        "techniques": [
          "T1078"
        ],
        "templateVersion": "1.0.0",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}