Detection rules › Kusto

Account Elevated to New Role

Severity
medium
Time window
14d
Group by
ElevatedRole, Id, TargetUserPrincipalName
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Persistence

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

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 auditList, let recentList and let newlyElevated are inlined into the numbered stages below.

Let binding: lookbackList used in Stage 7

let lookbackList = auditList
| where TimeGenerated between(ago(14d)..ago(1d));

Stages 1 to 7 define let newlyElevated (the rule's main pipeline source); stages 8 to 18 run on it.

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

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

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
OperationNameeqAdd member to role completed (PIM activation)excludes:OperationName field:"OperationName" value:"Add member to role completed (PIM activation)"
Resulteqsuccessexcludes:Result field:"Result" value:"success"

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
Idproject
InitiatingIPAddressextend
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
ElevatedByextend
ElevatedUserextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend
TargetAccountNameextend
TargetAccountUPNSuffixextend