Multiple Suspicious Modificationof Global Admin Properties
Query
let query_frequency = 1h;
let query_period = 14d;
let _ExpectedEvents =
_GetWatchlist("Activity-ExpectedSignificantActivity")
| where Activity == "GlobalAdminUpdate"
| project InitiatorId = tostring(ActorId), OperationName = tostring(Auxiliar)
;
IdentityInfo
| where TimeGenerated > ago(query_period)
| where set_has_element(AssignedRoles, "Global Administrator")
| distinct AccountUPN, AccountObjectId
| extend AccountUPN = tolower(AccountUPN)
| join kind=inner (
AuditLogs
| where TimeGenerated > ago(query_frequency)
| where Category == "UserManagement" and AADOperationType == "Update" // and Result =~ "success"
| where not(LoggedByService == "Access Reviews")
// | where isnotempty(InitiatedBy["user"])
| mv-expand TargetResource = TargetResources
| where TargetResource["type"] == "User"
| extend AccountObjectId = tostring(TargetResource["id"])
| where not(LoggedByService == "Self-service Password Management" and OperationName == "Self-service password reset flow activity progress" and (Result == "failure" or ResultDescription == "User submitted their user ID"))
| where not(LoggedByService == "Core Directory" and OperationName has "update" and 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(LoggedByService == "Core Directory" and OperationName has "update" and tostring(modifiedProperties["Action Client Name"]["newValue"]) == "DirectorySync")
| where not(LoggedByService == "Core Directory" and OperationName has "Update user" and isnotempty(modifiedProperties["Included Updated Properties"]) and tostring(modifiedProperties["Included Updated Properties"]["newValue"]) in ("LastDirSyncTime", ""))
| where not(LoggedByService == "Core Directory" and OperationName == "Update user" and 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"])
| join kind=leftanti _ExpectedEvents on InitiatorId, OperationName
) on AccountObjectId
| project
TimeGenerated,
LoggedByService,
Category,
Initiator,
IPAddress,
OperationName,
Result,
ResultDescription,
AccountUPN,
InitiatedBy,
AdditionalDetails,
TargetResources,
AccountObjectId,
InitiatorId,
CorrelationIdExplanation
This KQL query is designed to identify and analyze specific user management activities related to "Global Administrator" roles within a Microsoft environment. Here's a simplified breakdown of what the query does:
-
Define Timeframes:
query_frequencyis set to 1 hour, meaning the query looks at data from the last hour.query_periodis set to 14 days, meaning the query considers data from the last 14 days.
-
Identify Expected Events:
- It retrieves a watchlist named "Activity-ExpectedSignificantActivity" to identify expected "GlobalAdminUpdate" activities. This list is used to filter out known expected events.
-
Filter Global Administrators:
- The query checks the
IdentityInfotable for users who have been assigned the "Global Administrator" role within the last 14 days. - It extracts distinct user identifiers and converts their usernames to lowercase for consistency.
- The query checks the
-
Audit Log Analysis:
- It examines the
AuditLogstable for user management activities that occurred in the last hour. - It filters logs to include only those related to user updates, excluding certain operations like those initiated by "Access Reviews" or "Self-service Password Management".
- It further refines the data by excluding specific types of updates that are considered routine or non-significant.
- It examines the
-
Join and Exclude Expected Events:
- The query joins the filtered audit logs with the list of global administrators, focusing on activities not listed in the expected events watchlist.
- It uses a left anti join to exclude any events that match the expected activities.
-
Output Relevant Information:
- Finally, it projects (selects) relevant columns such as the time of the event, service that logged the event, category, initiator details, IP address, operation name, result, and other details for further analysis.
In essence, this query is designed to detect and report unexpected or potentially suspicious updates made by or to global administrators, excluding known and expected activities, to help in security monitoring and auditing.
Details

Jose Sebastián Canós
Released: August 12, 2025
Tables
IdentityInfoAuditLogs
Keywords
ActivityIdentityInfoAuditLogsUserManagementUserTargetResourcesGlobalAdminUpdateGlobalAdministratorAccountUPNAccountObjectIdInitiatorIdOperationNameInitiatedByIPAddressResultResultDescriptionAdditionalDetailsCorrelationId
Operators
letwhereprojecttolowerjoinkind=innerkind=leftantimv-expandmv-applysummarizemake_bagbag_packtrimisnotemptytostringhasinarray_sort_ascextract_alliifbag_keysonagodistinctextendnot==!==~>andor