Adversaries may modify local admin group memberships to gain or maintain elevated privileges within a system. SOC teams should proactively hunt for these changes in Azure Sentinel to identify potential privilege escalation attempts and unauthorized access.
KQL Query
let ADAZUsers = IdentityInfo
| extend DirectoryDomain = AccountDomain
| extend DirectoryAccount = AccountName
| extend OnPremSid = AccountSID
| distinct DirectoryDomain , DirectoryAccount , OnPremSid , AccountCloudSID, AccountUPN, GivenName, Surname;
// check for any new created or modified local accounts
let NewUsers = DeviceEvents
| where ActionType contains "UserAccountCreated" // or ActionType contains "UserAccountModified"
| extend lUserAdded = AccountName
| extend NewUserSID = AccountSid
| extend laccountdomain = AccountDomain
| distinct NewUserSID, lUserAdded,laccountdomain;
// Check for any local group changes and enrich the data with the account name obtained from the previous query
DeviceEvents
| where ActionType == 'UserAccountAddedToLocalGroup'
| extend AddedAccountSID = tostring(parse_json(AdditionalFields).MemberSid)
| extend LocalGroup = AccountName
| extend LocalGroupSID = AccountSid
| extend Actor = trim(@"[^\w]+",InitiatingProcessAccountName)
// limit to local administrators group
// | where LocalGroupSID contains "S-1-5-32-544"
| join kind= leftouter (NewUsers)
on $left.AddedAccountSID == $right.NewUserSID
| project Timestamp, DeviceName, LocalGroup,LocalGroupSID, AddedAccountSID, lUserAdded , Actor, ActionType , laccountdomain
| join kind= leftouter (ADAZUsers)
on $left.AddedAccountSID == $right.OnPremSid
| extend UserAdded = iff(isnotempty(lUserAdded),strcat(laccountdomain,"\\", lUserAdded), strcat(DirectoryDomain,"\\", DirectoryAccount))
| project Timestamp, DeviceName, LocalGroup,LocalGroupSID, AddedAccountSID, UserAdded , Actor, ActionType
| where DeviceName !contains Actor
// Provide details on actors that added users
// | summarize count() by Actor
// | join ADAZUsers
// on $left.Actor == $right.DirectoryAccount
// | render piechart
id: dd2c4f48-b732-4a75-b2c4-b44bacc66d00
name: LocalAdminGroupChanges
description: |
Author: alex verboon @alexverboon.
Blogpost: https://www.verboon.info/2020/09/hunting-for-local-group-membership-changes.
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- IdentityInfo
- DeviceEvents
query: |
let ADAZUsers = IdentityInfo
| extend DirectoryDomain = AccountDomain
| extend DirectoryAccount = AccountName
| extend OnPremSid = AccountSID
| distinct DirectoryDomain , DirectoryAccount , OnPremSid , AccountCloudSID, AccountUPN, GivenName, Surname;
// check for any new created or modified local accounts
let NewUsers = DeviceEvents
| where ActionType contains "UserAccountCreated" // or ActionType contains "UserAccountModified"
| extend lUserAdded = AccountName
| extend NewUserSID = AccountSid
| extend laccountdomain = AccountDomain
| distinct NewUserSID, lUserAdded,laccountdomain;
// Check for any local group changes and enrich the data with the account name obtained from the previous query
DeviceEvents
| where ActionType == 'UserAccountAddedToLocalGroup'
| extend AddedAccountSID = tostring(parse_json(AdditionalFields).MemberSid)
| extend LocalGroup = AccountName
| extend LocalGroupSID = AccountSid
| extend Actor = trim(@"[^\w]+",InitiatingProcessAccountName)
// limit to local administrators group
// | where LocalGroupSID contains "S-1-5-32-544"
| join kind= leftouter (NewUsers)
on $left.AddedAccountSID == $right.NewUserSID
| project Timestamp, DeviceName, LocalGroup,LocalGroupSID, AddedAccountSID, lUserAdded , Actor, ActionType , laccountdomain
| join kind= leftouter (ADAZUsers)
on $left.AddedAccountSID == $right.OnPremSid
| extend UserAdded = iff(isnotempty(lUserAdded),strcat(laccountdomain,"\\", lUserAdded), strcat(DirectoryDomain,"\\", DirectoryAccount))
| project Timestamp, DeviceName, LocalGroup,LocalGroupSID, AddedAccountSID, UserAdded , Actor, ActionType
| where DeviceName !contains Actor
// Provide details on actors that added users
// | summarize count() by Actor
// | join ADAZUsers
// on $left.Actor == $right.DirectoryAccount
// | render piechart
version: 1.0.0
| Sentinel Table | Notes |
|---|---|
DeviceEvents | Ensure this data connector is enabled |
IdentityInfo | Ensure this data connector is enabled |
Scenario: Scheduled Job Updates Local Admin Group
Description: A scheduled task (e.g., Task Scheduler) runs a script that adds or removes a user from the Local Admin group as part of a routine maintenance or user lifecycle management process.
Filter/Exclusion: Check for Event ID 41 with Task Scheduler as the source, or filter by CommandLine containing known maintenance scripts (e.g., UpdateUserGroup.ps1).
Scenario: User is Added to Local Admin Group via PowerShell Remotely
Description: An admin uses PowerShell remoting (Invoke-Command) to modify the Local Admin group on a remote machine, which may be part of a bulk user management process.
Filter/Exclusion: Filter by ProcessName containing powershell.exe and check for RemoteAddress or Command that includes Invoke-Command or Add-LocalGroupMember.
Scenario: Group Policy Object (GPO) Deployment Affects Local Admin Membership
Description: A Group Policy Object (GPO) is applied that modifies local group membership, such as adding a user to the Local Admin group as part of a policy change.
Filter/Exclusion: Check for Event ID 6008 or Event ID 6006 with Group Policy in the description, or filter by Source field containing GroupPolicy.
Scenario: System Restore or System Image Rollback Modifies Group Membership
Description: A system restore or image rollback operation may reset group membership settings, including the Local Admin group.
Filter/Exclusion: Filter by Event ID 6008 or Event ID 6006 with System Restore or System Image in the event description.
**Scenario: Local Admin Group Modified