Detection rules › Kusto
Suspicious modification of Global Administrator user properties
This query will detect if user properties of Global Administrator are updated by an existing user. Usually only user administrator or other global administrator can update such properties. Investigate if such user change is an attempt to elevate an existing low privileged identity or rogue administrator activity
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry coverage
| Provider | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | IdentityInfo action any: Identity information |
| Entra-AuditLogs | Update user |
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
Rule body
id: 48602a24-67cf-4362-b258-3f4249e55def
name: Suspicious modification of Global Administrator user properties
description: |
'This query will detect if user properties of Global Administrator are updated by an existing user. Usually only user administrator or other global administrator can update such properties.
Investigate if such user change is an attempt to elevate an existing low privileged identity or rogue administrator activity'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078.004
query: |
let query_frequency = 1h;
let query_period = 14d;
IdentityInfo
| where TimeGenerated > ago(query_period)
| where set_has_element(AssignedRoles, "Global Administrator")
| distinct AccountUPN, AccountObjectId
| join kind=inner (
AuditLogs
| where TimeGenerated > ago(query_frequency)
| where OperationName=~ "Update user" and Result =~ "success"
// | where isnotempty(InitiatedBy["user"])
| mv-expand TargetResource = TargetResources
| where TargetResource["type"] == "User"
| extend AccountObjectId = tostring(TargetResource["id"])
| where tostring(TargetResource["modifiedProperties"]) != "[]"
| mv-apply modifiedProperty = TargetResource["modifiedProperties"] on (
summarize modifiedProperties = make_bag(
bag_pack(tostring(modifiedProperty["displayName"]),
bag_pack("oldValue", trim(@'[\"\s]+', tostring(modifiedProperty["oldValue"])),
"newValue", trim(@'[\"\s]+', tostring(modifiedProperty["newValue"])))))
)
| where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) in ("LastDirSyncTime", ""))
| where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) == "StrongAuthenticationPhoneAppDetail" and isnotempty(modifiedProperties["StrongAuthenticationPhoneAppDetail"]) and tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["newValue"])))) == tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["oldValue"])))))
| extend
Initiator = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["displayName"]), tostring(InitiatedBy["user"]["userPrincipalName"])),
InitiatorId = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["servicePrincipalId"]), tostring(InitiatedBy["user"]["id"])),
IPAddress = tostring(InitiatedBy[tostring(bag_keys(InitiatedBy)[0])]["ipAddress"])
) on AccountObjectId
| project TimeGenerated, Category, Identity, Initiator, IPAddress, OperationName, Result, AccountUPN, InitiatedBy, AdditionalDetails, TargetResources, AccountObjectId, InitiatorId, CorrelationId
| extend
InitiatorName = tostring(split(Initiator, "@")[0]),
InitiatorUPNSuffix = tostring(split(Initiator, "@")[1]),
AccountName = tostring(split(AccountUPN, "@")[0]),
AccountUPNSuffix = tostring(split(AccountUPN, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Initiator
- identifier: Name
columnName: InitiatorName
- identifier: UPNSuffix
columnName: InitiatorUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUPN
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Parameters
let query_frequency = 1h;
let query_period = 14d;
Stage 1: source
IdentityInfo
Stage 2: where
| where TimeGenerated > ago(query_period)
Stage 3: where
| where set_has_element(AssignedRoles, "Global Administrator")
Stage 4: distinct
| distinct AccountUPN, AccountObjectId
Stage 5: join
| join kind=inner (
AuditLogs
| where TimeGenerated > ago(query_frequency)
| where OperationName=~ "Update user" and Result =~ "success"
| mv-expand TargetResource = TargetResources
| where TargetResource["type"] == "User"
| extend AccountObjectId = tostring(TargetResource["id"])
| where tostring(TargetResource["modifiedProperties"]) != "[]"
| mv-apply modifiedProperty = TargetResource["modifiedProperties"] on (
summarize modifiedProperties = make_bag(
bag_pack(tostring(modifiedProperty["displayName"]),
bag_pack("oldValue", trim(@'[\"\s]+', tostring(modifiedProperty["oldValue"])),
"newValue", trim(@'[\"\s]+', tostring(modifiedProperty["newValue"])))))
)
| where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) in ("LastDirSyncTime", ""))
| where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) == "StrongAuthenticationPhoneAppDetail" and isnotempty(modifiedProperties["StrongAuthenticationPhoneAppDetail"]) and tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["newValue"])))) == tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["oldValue"])))))
| extend
Initiator = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["displayName"]), tostring(InitiatedBy["user"]["userPrincipalName"])),
InitiatorId = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["servicePrincipalId"]), tostring(InitiatedBy["user"]["id"])),
IPAddress = tostring(InitiatedBy[tostring(bag_keys(InitiatedBy)[0])]["ipAddress"])
) on AccountObjectId
Stage 6: project
| project TimeGenerated, Category, Identity, Initiator, IPAddress, OperationName, Result, AccountUPN, InitiatedBy, AdditionalDetails, TargetResources, AccountObjectId, InitiatorId, CorrelationId
Stage 7: extend
| extend
InitiatorName = tostring(split(Initiator, "@")[0]),
InitiatorUPNSuffix = tostring(split(Initiator, "@")[1]),
AccountName = tostring(split(AccountUPN, "@")[0]),
AccountUPNSuffix = tostring(split(AccountUPN, "@")[1])
Stage 8: summarize aggregation inside the mv-apply branch
summarize
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
StrongAuthenticationPhoneAppDetail | is_not_null | excludes:StrongAuthenticationPhoneAppDetail | |
newValue | eq | StrongAuthenticationPhoneAppDetail | excludes:newValue field:"newValue" value:"StrongAuthenticationPhoneAppDetail" |
newValue | in | LastDirSyncTime | excludes:newValue field:"newValue" value:"LastDirSyncTime" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AssignedRoles | in |
| field:"AssignedRoles" kind:in value:"Global Administrator" |
OperationName | eq |
| field:"OperationName" kind:eq value:"Update user" |
Result | eq |
| field:"Result" kind:eq value:"success" |
modifiedProperties | ne |
| field:"azure_ad::modified_properties_new" kind:ne value:"[]" |
type | eq |
| field:"type" kind:eq value:"User" |