Query Details

HQ 016 Privileged Role Change Risk Event Correlation

Query

// HQ-016 — Privileged Role Change Correlated with User Risk Events
// Purpose  : Cross-correlate privileged role assignment / removal events with
//            AADUserRiskEvents for the same user within a ±2-hour window.
//            Two attacker scenarios are covered:
//              A) Risk event detected THEN attacker elevates the compromised account
//                 (RiskEvent → RoleChange) — confirming post-compromise privilege escalation.
//              B) Role assignment made THEN Entra ID detects risky behaviour shortly after
//                 (RoleChange → RiskEvent) — may indicate the newly-elevated account
//                 performing suspicious actions.
// Tables   : AuditLogs, AADUserRiskEvents
// MITRE    : TA0004 Privilege Escalation — T1078.004 Valid Accounts: Cloud Accounts
//            TA0003 Persistence — T1098.003 Additional Cloud Roles
// Author   : Custom
// ---------------------------------------------------------------------------

let LookbackDays       = 14d;
let CorrelationWindow  = 2h;
let PrivilegedRoles = dynamic([
    "Global Administrator",
    "Security Administrator",
    "Privileged Role Administrator",
    "User Administrator",
    "Authentication Administrator",
    "Privileged Authentication Administrator",
    "Exchange Administrator",
    "SharePoint Administrator",
    "Compliance Administrator",
    "Conditional Access Administrator",
    "Application Administrator",
    "Cloud Application Administrator",
    "Helpdesk Administrator",
    "Password Administrator"
]);
let RoleChangeActivities = dynamic([
    "Add member to role",
    "Remove member from role",
    "Add eligible member to role",
    "Remove eligible member from role",
    "Add scoped member to role",
    "Remove scoped member from role",
    "Add permanent member to role",
    "Remove permanent member from role",
    "Add member to role outside of PIM"
]);
// ── Step 1: Privileged role assignment / removal events ────────────────────
let PrivRoleChanges = AuditLogs
| where TimeGenerated > ago(LookbackDays)
| where Category == "RoleManagement"
| where ActivityDisplayName in~ (RoleChangeActivities)
| extend InitiatorUPN = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatorApp = tostring(InitiatedBy.app.displayName)
// Expand TargetResources to extract RoleName and TargetUserUPN
| mv-expand TargetResource = TargetResources
| extend ResType    = tostring(TargetResource.type)
| extend ResName    = tostring(TargetResource.displayName)
| extend ResUserUPN = tostring(TargetResource.userPrincipalName)
| summarize
    RoleName      = max(iff(ResType == "Role", ResName, "")),
    TargetUserUPN = tolower(max(iff(isnotempty(ResUserUPN), ResUserUPN, "")))
    by  TimeGenerated, ActivityDisplayName, LoggedByService, Result,
        InitiatorUPN, InitiatorApp, Id
| where RoleName in (PrivilegedRoles) and isnotempty(TargetUserUPN);
// ── Step 2: User risk detection events ────────────────────────────────────
let UserRiskEvents = AADUserRiskEvents
| where TimeGenerated > ago(LookbackDays)
| where RiskLevel in ("high", "medium")
| project
    RiskTime       = TimeGenerated,
    RiskyUPN       = tolower(UserPrincipalName),
    RiskEventType,
    RiskLevel,
    RiskState,
    RiskIpAddress  = IpAddress;
// ── Step 3: Inner join — same user, events within CorrelationWindow ────────
PrivRoleChanges
| join kind=inner (UserRiskEvents)
    on $left.TargetUserUPN == $right.RiskyUPN
// Filter to events within the bidirectional correlation window
| where TimeGenerated between ((RiskTime - CorrelationWindow) .. (RiskTime + CorrelationWindow))
| extend TimeDiffMinutes = abs(datetime_diff('minute', TimeGenerated, RiskTime))
// Label the sequence ordering
| extend EventSequence = case(
    TimeGenerated < RiskTime,  "RoleChange THEN RiskDetected",
    TimeGenerated > RiskTime,  "RiskDetected THEN RoleChange",
    "Simultaneous"
)
| extend OperationType = iff(ActivityDisplayName has "Add", "Role Assigned", "Role Removed")
| project
    RoleChangeTime   = TimeGenerated,
    RoleName,
    TargetUserUPN,
    OperationType,
    LoggedByService,
    InitiatorUPN,
    InitiatorApp,
    Result,
    RiskTime,
    RiskEventType,
    RiskLevel,
    RiskState,
    RiskIpAddress,
    EventSequence,
    TimeDiffMinutes,
    RoleChangeId     = Id
| sort by RoleChangeTime desc

Explanation

This query is designed to identify potential security threats by analyzing changes in privileged roles and user risk events within a two-hour window. Here's a simplified breakdown of what it does:

  1. Purpose: The query aims to detect two specific scenarios involving user accounts:

    • Scenario A: A risk event is detected first, followed by a change in the user's privileged role, suggesting that an attacker might have elevated the compromised account's privileges after gaining access.
    • Scenario B: A privileged role is assigned first, and then a risk event is detected, indicating that the newly elevated account might be engaging in suspicious activities.
  2. Data Sources: It uses data from two tables:

    • AuditLogs: To track changes in privileged roles.
    • AADUserRiskEvents: To detect user risk events.
  3. Steps:

    • Step 1: Extracts events related to the assignment or removal of privileged roles from the AuditLogs table. It focuses on specific roles like "Global Administrator" and activities like "Add member to role."
    • Step 2: Extracts user risk events from the AADUserRiskEvents table, focusing on events with a "high" or "medium" risk level.
    • Step 3: Joins the two datasets to find instances where the same user has both a role change and a risk event within a two-hour window. It labels the sequence of events to identify whether the role change happened before or after the risk event.
  4. Output: The query provides details about the role change and risk event, including the time, type of operation (role assigned or removed), and sequence of events. It sorts the results by the time of the role change, helping security analysts quickly identify and investigate potential threats.

Details

David Alonso profile picture

David Alonso

Released: April 6, 2026

Tables

AuditLogsAADUserRiskEvents

Keywords

PrivilegedRoleChangeUserRiskEventsAuditLogsAADUserRiskEventsRoleManagementRoleNameRiskLevelRiskStateRiskIPAddressTimeGeneratedActivityDisplayNameLoggedByServiceInitiatorUPNInitiatorAppTargetResourcesRoleNameTargetUserUPNRiskTimeRiskyUPNRiskEventTypeOperationTypeResultEventSequenceTimeDiffMinutesRoleChangeId

Operators

letdynamicagoin~tostringmv-expandiffisnotemptytolowersummarizemaxbyprojectjoinkind=inneronbetween..extendabsdatetime_diffcasehassort bydesc

Actions