Detection rules › Kusto

Multiple admin membership removals from newly created admin.

Status
available
Severity
medium
Time window
7d
Group by
Initiator, OperationName, Result, RoleName, Target
Source
github.com/Azure/Azure-Sentinel

This query detects when newly created Global admin removes multiple existing global admins which can be an attempt by adversaries to lock down organization and retain sole access. Investigate reasoning and intention of multiple membership removal by new Global admins and take necessary actions accordingly.

MITRE ATT&CK coverage

TacticTechniques
Impact

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: cda5928c-2c1e-4575-9dfa-07568bc27a4f
name: Multiple admin membership removals from newly created admin.
description: |
  'This query detects when newly created Global admin removes multiple existing global admins which can be an attempt by adversaries to lock down organization and retain sole access. 
   Investigate reasoning and intention of multiple membership removal by new Global admins and take necessary actions accordingly.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Impact
relevantTechniques:
  - T1531
tags:
  - DEV-0537
query: |
  let lookback = 7d; 
  let timeframe = 1h; 
  let GlobalAdminsRemoved = AuditLogs 
  | where TimeGenerated > ago(timeframe) 
  | where Category =~ "RoleManagement" 
  | where AADOperationType in ("Unassign", "RemoveEligibleRole") 
  | where ActivityDisplayName has_any ("Remove member from role", "Remove eligible member from role") 
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "User"
        | extend Target = tostring(TargetResource.userPrincipalName),
                 props = TargetResource.modifiedProperties
    )
  | mv-apply Property = props on 
        (
            where Property.displayName =~ "Role.DisplayName"
            | extend RoleName = trim('"',tostring(Property.oldValue))
        )
  | where RoleName =~ "Global Administrator" // Add other Privileged role if applicable
  | 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 Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName) 
  | where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"  // Filtering PIM events  
  | summarize RemovedGlobalAdminTime = max(TimeGenerated), TargetAdmins = make_set(Target,100) by OperationName, RoleName, Initiator, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, Result; 
  let GlobalAdminsAdded = AuditLogs 
  | where TimeGenerated > ago(lookback) 
  | where Category =~ "RoleManagement" 
  | where AADOperationType in ("Assign", "AssignEligibleRole") 
  | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role") and Result == "success" 
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "User"
        | extend Target = tostring(TargetResource.userPrincipalName),
                 props = TargetResource.modifiedProperties
    )
  | mv-apply Property = props on 
        (
            where Property.displayName =~ "Role.DisplayName"
            | extend RoleName = trim('"',tostring(Property.newValue))
        )
  | where RoleName =~ "Global Administrator" // Add other Privileged role if applicable
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, tostring(InitiatedBy.user.userPrincipalName)) 
  | where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"  // Filtering PIM events 
  | summarize AddedGlobalAdminTime = max(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result;
  GlobalAdminsAdded 
  | join kind= inner GlobalAdminsRemoved on $left.Target == $right.Initiator 
  | where AddedGlobalAdminTime < RemovedGlobalAdminTime 
  | extend NoofAdminsRemoved = array_length(TargetAdmins) 
  | where NoofAdminsRemoved > 1
  | project AddedGlobalAdminTime, Initiator, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, Target, RemovedGlobalAdminTime, TargetAdmins, NoofAdminsRemoved
  | extend TargetName = tostring(split(Target,'@',0)[0]), TargetUPNSuffix = tostring(split(Target,'@',1)[0])
  | extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Target
      - 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.3
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 7d;
let timeframe = 1h;

let GlobalAdminsAdded is inlined into the numbered stages below.

Let binding: GlobalAdminsRemoved used in Stage 13

let GlobalAdminsRemoved = AuditLogs 
| where TimeGenerated > ago(timeframe) 
| where Category =~ "RoleManagement" 
| where AADOperationType in ("Unassign", "RemoveEligibleRole") 
| where ActivityDisplayName has_any ("Remove member from role", "Remove eligible member from role") 
| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "User"
      | extend Target = tostring(TargetResource.userPrincipalName),
               props = TargetResource.modifiedProperties
  )
| mv-apply Property = props on 
      (
          where Property.displayName =~ "Role.DisplayName"
          | extend RoleName = trim('"',tostring(Property.oldValue))
      )
| where RoleName =~ "Global Administrator"
| 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 Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName) 
| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"
| summarize RemovedGlobalAdminTime = max(TimeGenerated), TargetAdmins = make_set(Target,100) by OperationName, RoleName, Initiator, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, Result;

Stages 1 to 12 define let GlobalAdminsAdded (the rule's main pipeline source); stages 13 to 19 run on it.

Stage 1: source

AuditLogs

Stage 2: where

| where TimeGenerated > ago(lookback)

Stage 3: where

| where Category =~ "RoleManagement"

Stage 4: where

| where AADOperationType in ("Assign", "AssignEligibleRole")

Stage 5: where

| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role") and Result == "success"

Stage 6: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "User"
      | extend Target = tostring(TargetResource.userPrincipalName),
               props = TargetResource.modifiedProperties
  )

Stage 7: kusto:mv-apply

| mv-apply Property = props on 
      (
          where Property.displayName =~ "Role.DisplayName"
          | extend RoleName = trim('"',tostring(Property.newValue))
      )

Stage 8: where

| where RoleName =~ "Global Administrator"

Stage 9: extend

| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)

Stage 10: extend

| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, tostring(InitiatedBy.user.userPrincipalName))

Stage 11: where

| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"

Stage 12: summarize

| summarize AddedGlobalAdminTime = max(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result

Stage 13: join

GlobalAdminsAdded
| join kind= inner GlobalAdminsRemoved on $left.Target == $right.Initiator

Stage 14: where

| where AddedGlobalAdminTime < RemovedGlobalAdminTime

Stage 15: extend

| extend NoofAdminsRemoved = array_length(TargetAdmins)

Stage 16: where

| where NoofAdminsRemoved > 1

Stage 17: project

| project AddedGlobalAdminTime, Initiator, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, Target, RemovedGlobalAdminTime, TargetAdmins, NoofAdminsRemoved

Stage 18: extend

| extend TargetName = tostring(split(Target,'@',0)[0]), TargetUPNSuffix = tostring(split(Target,'@',1)[0])

Stage 19: extend

| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AADOperationTypein
  • Assign
  • AssignEligibleRole
  • RemoveEligibleRole
  • Unassign
field:"AADOperationType" kind:in
ActivityDisplayNamematch
  • Add eligible member to role transforms: term
  • Add member to role transforms: term
  • Remove eligible member from role transforms: term
  • Remove member from role transforms: term
field:"azure_ad::activity_display_name" kind:match
AddedGlobalAdminTimecross_field_compare
  • RemovedGlobalAdminTime transforms: op:lt
field:"AddedGlobalAdminTime" kind:cross_field_compare value:"RemovedGlobalAdminTime"
Categoryeq
  • RoleManagement
field:"Category" kind:eq value:"RoleManagement"
Initiatorne
  • MS-PIM
  • MS-PIM-Fairfax
field:"Initiator" kind:ne
NoofAdminsRemovedgt
  • 1
field:"NoofAdminsRemoved" kind:gt value:"1"
Resulteq
  • success
field:"Result" kind:eq value:"success"
RoleNameeq
  • Global Administrator
field:"RoleName" kind:eq value:"Global Administrator"
displayNameeq
  • Role.DisplayName
field:"displayName" kind:eq value:"Role.DisplayName"
typeeq
  • User
field:"type" kind:eq value:"User"

Output fields

These fields are emitted when the rule matches.

FieldSource
AddedGlobalAdminTimeproject
InitiatingAadUserIdproject
InitiatingAppNameproject
InitiatingAppServicePrincipalIdproject
InitiatingIpAddressproject
InitiatingUserPrincipalNameproject
Initiatorproject
NoofAdminsRemovedproject
RemovedGlobalAdminTimeproject
Targetproject
TargetAdminsproject
TargetNameextend
TargetUPNSuffixextend
InitiatedByNameextend
InitiatedByUPNSuffixextend