Local Admin Group Changes
| Id | 63aa43c2-e88e-4102-aea5-0432851c541a |
| Rulename | Local Admin Group Changes |
| Description | This query searches for changes to the local administrators group. Blogpost: https://www.verboon.info/2020/09/hunting-for-local-group-membership-changes. |
| Severity | High |
| Tactics | Persistence |
| Techniques | T1098 |
| Required data connectors | MicrosoftThreatProtection |
| Kind | Scheduled |
| Query frequency | 1h |
| Query period | 1h |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Defender XDR/Analytic Rules/Persistence/LocalAdminGroupChanges.yaml |
| Version | 1.0.2 |
| Arm template | 63aa43c2-e88e-4102-aea5-0432851c541a.json |
let machineAccountSIDs = dynamic([
"S-1-5-18",
"S-1-5-20",
"S-1-5-19"]);
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'
// Exclude machine and wellknown SIDs
| where (AccountSid !in (machineAccountSIDs)) and (AccountSid matches regex @"S-\d-\d+-\d+-(\d+-){1,5}\d+")
| extend LocalGroupSID = tostring(parse_json(AdditionalFields).GroupSid)
| extend LocalGroup = tostring(parse_json(AdditionalFields).GroupName)
| extend AddedAccountSID = 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 TimeGenerated, DeviceName, LocalGroup,LocalGroupSID, AddedAccountSID, lUserAdded , Actor, ActionType , laccountdomain
| join kind=innerunique (ADAZUsers)
on $left.AddedAccountSID == $right.OnPremSid
| extend UserAdded = iff(isnotempty(lUserAdded),strcat(laccountdomain,"\\", lUserAdded), strcat(DirectoryDomain,"\\", DirectoryAccount))
| extend AccountName = iff(isnotempty(lUserAdded), lUserAdded, DirectoryAccount)
| project TimeGenerated, DeviceName, LocalGroup, LocalGroupSID, AddedAccountSID, UserAdded ,Actor, ActionType, AccountName, laccountdomain
| where DeviceName !contains Actor
// Provide details on actors that added users
// | summarize count() by Actor
// | join ADAZUsers
// on $left.Actor == $right.DirectoryAccount
// | render piechart
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
version: 1.0.2
queryFrequency: 1h
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Defender XDR/Analytic Rules/Persistence/LocalAdminGroupChanges.yaml
name: Local Admin Group Changes
triggerThreshold: 0
tactics:
- Persistence
severity: High
status: Available
kind: Scheduled
requiredDataConnectors:
- dataTypes:
- IdentityInfo
- DeviceEvents
connectorId: MicrosoftThreatProtection
entityMappings:
- fieldMappings:
- columnName: DeviceName
identifier: FullName
- columnName: HostName
identifier: HostName
- columnName: DnsDomain
identifier: DnsDomain
entityType: Host
- fieldMappings:
- columnName: UserAdded
identifier: FullName
- columnName: AccountName
identifier: Name
- columnName: laccountdomain
identifier: NTDomain
entityType: Account
id: 63aa43c2-e88e-4102-aea5-0432851c541a
query: |
let machineAccountSIDs = dynamic([
"S-1-5-18",
"S-1-5-20",
"S-1-5-19"]);
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'
// Exclude machine and wellknown SIDs
| where (AccountSid !in (machineAccountSIDs)) and (AccountSid matches regex @"S-\d-\d+-\d+-(\d+-){1,5}\d+")
| extend LocalGroupSID = tostring(parse_json(AdditionalFields).GroupSid)
| extend LocalGroup = tostring(parse_json(AdditionalFields).GroupName)
| extend AddedAccountSID = 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 TimeGenerated, DeviceName, LocalGroup,LocalGroupSID, AddedAccountSID, lUserAdded , Actor, ActionType , laccountdomain
| join kind=innerunique (ADAZUsers)
on $left.AddedAccountSID == $right.OnPremSid
| extend UserAdded = iff(isnotempty(lUserAdded),strcat(laccountdomain,"\\", lUserAdded), strcat(DirectoryDomain,"\\", DirectoryAccount))
| extend AccountName = iff(isnotempty(lUserAdded), lUserAdded, DirectoryAccount)
| project TimeGenerated, DeviceName, LocalGroup, LocalGroupSID, AddedAccountSID, UserAdded ,Actor, ActionType, AccountName, laccountdomain
| where DeviceName !contains Actor
// Provide details on actors that added users
// | summarize count() by Actor
// | join ADAZUsers
// on $left.Actor == $right.DirectoryAccount
// | render piechart
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
queryPeriod: 1h
triggerOperator: gt
relevantTechniques:
- T1098
description: |
This query searches for changes to the local administrators group.
Blogpost: https://www.verboon.info/2020/09/hunting-for-local-group-membership-changes.