Query Details

HUNT 06 Orphaned Privileged Roles

Query

// Hunt     : Hunt - Privileged Role Assignments Matching Disabled or Deleted AAD Identities
// Tactics  : Persistence,PrivilegeEscalation
// MITRE    : T1098.003
// Purpose  : Finds role assignments made AFTER the target identity was disabled or deleted. This indicates either an attacker re-using a known principal ID to maintain access, or a cleanup gap creating a dormant privilege.
//==========================================================================================

let DisabledUsers = AuditLogs
    | where TimeGenerated > ago(90d)
    | where Result =~ "success"
    // FIX: previous version treated every "Update user" as a disable, correlating benign
    // profile edits as "disabled identity" and generating false hits. Restrict to the two
    // authoritative operations. (Extend with modifiedProperties AccountEnabled=false parsing
    // if your tenant needs the soft-disable path.)
    | where OperationName in~ ("Disable account", "Delete user")
    | extend TargetId = tostring(TargetResources[0].id), TargetUPN = tostring(TargetResources[0].userPrincipalName)
    | summarize DisabledAt = max(ActivityDateTime) by TargetId, TargetUPN;
AzureActivity
| where TimeGenerated > ago(90d)
| where OperationNameValue =~ "MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE"
| where ActivityStatusValue =~ "Success"
| extend AssignedPrincipalId = tostring(parse_json(Properties).requestbody.properties.principalId)
| extend RoleDefId = tostring(parse_json(Properties).requestbody.properties.roleDefinitionId)
| where isnotempty(AssignedPrincipalId)
| join kind=inner DisabledUsers on $left.AssignedPrincipalId == $right.TargetId
| where TimeGenerated > DisabledAt
| project TimeGenerated, Caller, AssignedPrincipalId, TargetUPN, RoleDefId, ResourceGroup, SubscriptionId, DisabledAt
| order by TimeGenerated desc

Explanation

This query is designed to identify suspicious role assignments in Azure Active Directory (AAD) that occur after a user account has been disabled or deleted. Here's a simplified breakdown of what the query does:

  1. Identify Disabled or Deleted Users:

    • It looks at audit logs from the past 90 days to find user accounts that have been either disabled or deleted.
    • It specifically checks for operations labeled "Disable account" or "Delete user" to ensure accuracy.
    • For each of these operations, it captures the user ID and user principal name, along with the latest date and time the account was disabled or deleted.
  2. Find Role Assignments:

    • It examines Azure activity logs from the past 90 days to find successful role assignments.
    • It focuses on operations related to role assignments, specifically those that involve writing new role assignments.
  3. Match Role Assignments to Disabled/Deleted Users:

    • It matches these role assignments to the previously identified disabled or deleted user accounts based on the user ID.
    • It ensures that the role assignment occurred after the user account was disabled or deleted.
  4. Output Suspicious Activity:

    • It outputs details of these suspicious role assignments, including the time they occurred, who made the assignment, the user ID and principal name involved, the role definition ID, and related resource and subscription information.
    • The results are sorted by the time the role assignment was made, in descending order.

The purpose of this query is to detect potential security issues, such as an attacker reusing a known user ID to maintain access or a gap in cleanup processes that leaves dormant privileges in place.

Details

David Alonso profile picture

David Alonso

Released: July 27, 2026

Tables

AuditLogsAzureActivity

Keywords

AuditLogsAzureActivityOperationNameValueActivityStatusValuePropertiesRequestbodyPrincipalIdRoleDefinitionIdTargetResourcesActivityDateTimeTargetIdTargetUPNResourceGroupSubscriptionIdCaller

Operators

let|where=~in~extendtostring()summarizemax()ago()parse_json()isnotempty()joinon==projectorder bydesc

MITRE Techniques

Actions

GitHub