Detection rules › Kusto
Account Elevated to New Role
Detects an account that is elevated to a new role where that account has not had that role in the last 14 days. Role elevations are a key mechanism for gaining permissions, monitoring which users have which roles, and for anomalies in those roles is useful for finding suspicious activity. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | T1078.004 Valid Accounts: Cloud Accounts |
Event coverage
Rule body kusto
id: c1c66f0b-5531-4a3e-a619-9d2f770ef730
name: Account Elevated to New Role
description: |
'Detects an account that is elevated to a new role where that account has not had that role in the last 14 days.
Role elevations are a key mechanism for gaining permissions, monitoring which users have which roles, and for anomalies in those roles is useful for finding suspicious activity.
Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let auditList =
AuditLogs
| where TimeGenerated >= ago(14d)
| where OperationName =~ "Add member to role completed (PIM activation)"
| where Result =~ "success"
| extend TargetUserPrincipalName = tostring(TargetResources[2].userPrincipalName)
| extend displayName = tostring(TargetResources[0].displayName)
| extend displayName2 = tostring(TargetResources[3].displayName)
| extend ElevatedRole = iif(displayName =~ "Member", displayName2, displayName)
;
let lookbackList = auditList
| where TimeGenerated between(ago(14d)..ago(1d))
;
let recentList = auditList
| where TimeGenerated > ago(1d)
;
let newlyElevated = recentList
| join kind = leftanti lookbackList on ElevatedRole, TargetUserPrincipalName
;
newlyElevated | project Id, AdditionalDetails
| mv-expand bagexpansion=array AdditionalDetails
| evaluate bag_unpack(AdditionalDetails)
| extend key = column_ifexists("key", ''), value = column_ifexists("value", '')
| evaluate pivot(key, make_set(value))
| extend ipaddr = todynamic(column_ifexists("ipaddr", ""))
| mv-expand ipaddr
| project Id, InitiatingIPAddress = tostring(ipaddr)
| join kind=rightouter newlyElevated on Id
| 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 = iff(isnotempty(tostring(InitiatedBy.user.ipAddress)), tostring(InitiatedBy.user.ipAddress), InitiatingIPAddress)
| extend ElevatedBy = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName)
| extend ElevatedUser = TargetUserPrincipalName
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
| project-reorder ElevatedUser, ElevatedRole, ResultReason, ElevatedBy, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUserPrincipalName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserPrincipalName
- identifier: Name
columnName: TargetAccountName
- identifier: UPNSuffix
columnName: TargetAccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIPAddress
version: 1.1.2
kind: Scheduled
Stages and Predicates
Let binding: lookbackList
let lookbackList = auditList
| where TimeGenerated between(ago(14d)..ago(1d));
Derived from auditList.
The stages below define let newlyElevated (the rule's main pipeline source).
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated >= ago(14d)
Stage 3: where
| where OperationName =~ "Add member to role completed (PIM activation)"
Stage 4: where
| where Result =~ "success"
Stage 5: extend (4 consecutive steps)
| extend TargetUserPrincipalName = tostring(TargetResources[2].userPrincipalName)
| extend displayName = tostring(TargetResources[0].displayName)
| extend displayName2 = tostring(TargetResources[3].displayName)
| extend ElevatedRole = iif(displayName =~ "Member", displayName2, displayName)
Stage 6: where
| where TimeGenerated > ago(1d)
Stage 7: join (negated)
| join kind = leftanti lookbackList on ElevatedRole, TargetUserPrincipalName
The stages below run on newlyElevated (the outer pipeline).
Stage 8: project
newlyElevated
| project Id, AdditionalDetails
Stage 9: mv-expand
| mv-expand bagexpansion=array AdditionalDetails
Stage 10: evaluate
| evaluate bag_unpack(AdditionalDetails)
Stage 11: extend
| extend key = column_ifexists("key", ''), value = column_ifexists("value", '')
Stage 12: evaluate
| evaluate pivot(key, make_set(value))
Stage 13: extend
| extend ipaddr = todynamic(column_ifexists("ipaddr", ""))
Stage 14: mv-expand
| mv-expand ipaddr
Stage 15: project
| project Id, InitiatingIPAddress = tostring(ipaddr)
Stage 16: join
| join kind=rightouter newlyElevated on Id
Stage 17: extend (9 consecutive steps)
| 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 = iff(isnotempty(tostring(InitiatedBy.user.ipAddress)), tostring(InitiatedBy.user.ipAddress), InitiatingIPAddress)
| extend ElevatedBy = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName)
| extend ElevatedUser = TargetUserPrincipalName
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
Stage 18: project-reorder
| project-reorder ElevatedUser, ElevatedRole, ResultReason, ElevatedBy, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUserPrincipalName
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
OperationName | eq | Add member to role completed (PIM activation) |
Result | eq | success |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
OperationName | eq |
|
Result | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
Id | project |
InitiatingIPAddress | extend |
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
ElevatedBy | extend |
ElevatedUser | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
TargetAccountName | extend |
TargetAccountUPNSuffix | extend |