Detection rules › Kusto
NRT PIM Elevation Request Rejected
Identifies when a user is rejected for a privileged role elevation via PIM. Monitor rejections for indicators of attacker compromise of the requesting account. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-identity-management
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | T1078.004 Valid Accounts: Cloud Accounts |
Event coverage
Rule body kusto
id: 5db427b2-f406-4274-b413-e9fcb29412f8
name: NRT PIM Elevation Request Rejected
description: |
'Identifies when a user is rejected for a privileged role elevation via PIM. Monitor rejections for indicators of attacker compromise of the requesting account.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-identity-management'
severity: High
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- Persistence
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
AuditLogs
| where ActivityDisplayName =~ 'Add member to role request denied (PIM activation)'
| mv-apply ResourceItem = TargetResources on
(
where ResourceItem.type =~ "Role"
| extend Role = trim(@'"',tostring(ResourceItem.displayName))
)
| mv-apply ResourceItem = TargetResources on
(
where ResourceItem.type =~ "User"
| extend TargetUserPrincipalName = trim(@'"',tostring(ResourceItem.userPrincipalName))
)
| where ResultReason != "RoleAssignmentExists"
| where isnotempty(InitiatedBy.user)
| 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 TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
| project-reorder TimeGenerated, TargetUserPrincipalName, Role, OperationName, Result, ResultDescription
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserPrincipalName
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatedByName
- identifier: UPNSuffix
columnName: InitiatedByUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.0.4
kind: NRT
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where ActivityDisplayName =~ 'Add member to role request denied (PIM activation)'
Stage 3: kusto:mv-apply
| mv-apply ResourceItem = TargetResources on
(
where ResourceItem.type =~ "Role"
| extend Role = trim(@'"',tostring(ResourceItem.displayName))
)
Stage 4: kusto:mv-apply
| mv-apply ResourceItem = TargetResources on
(
where ResourceItem.type =~ "User"
| extend TargetUserPrincipalName = trim(@'"',tostring(ResourceItem.userPrincipalName))
)
Stage 5: where
| where ResultReason != "RoleAssignmentExists"
Stage 6: where
| where isnotempty(InitiatedBy.user)
Stage 7: extend (7 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 = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
Stage 8: project-reorder
| project-reorder TimeGenerated, TargetUserPrincipalName, Role, OperationName, Result, ResultDescription
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 |
|---|---|---|
ActivityDisplayName | eq |
|
ResultReason | ne |
|
type | eq |
|
user | is_not_null |
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 |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
TargetName | extend |
TargetUPNSuffix | extend |
InitiatedByName | extend |
InitiatedByUPNSuffix | extend |