Security Event Activity With Monitored AD Group
Query
let _ADGroups =
_GetWatchlist("SID-AuditADObjects")
| where Notes has ("[GroupActivity]")
| project GroupSID = SID, SAMAccountName, Auditors = Auditor, MonitoredEventID, Severity
| mv-apply split(MonitoredEventID, " ") to typeof(string) on (
summarize
take_any(*),
ValidEventIDs = make_list_if(MonitoredEventID, isnotempty(MonitoredEventID) and not(MonitoredEventID startswith "-")),
ExcludedEventIDs = make_list_if(replace_string(MonitoredEventID, "-", ""), isnotempty(MonitoredEventID) and MonitoredEventID startswith "-")
)
;
let _ExpectedActivityADGroups =
_GetWatchlist("Activity-ExpectedSignificantActivity")
| where Activity == "ADGroupEventID"
| project
Activity = tostring(Auxiliar),
ActorAccount = tostring(ActorPrincipalName),
GroupSid = tostring(DestinationResource)
;
let _ExpectedADGroupDomains = toscalar(
_GetWatchlist("Activity-ExpectedSignificantActivity")
| where Activity == "ADDomain" and Notes has "[MonitorADGroupFromDomain]"
| summarize make_list(Auxiliar)
);
let _AccountOperators = toscalar(
_GetWatchlist("Activity-ExpectedSignificantActivity")
| where Activity == "AccountOperator"
| summarize make_list(ActorPrincipalName)
);
let add_member_eventids = dynamic([4728, 4732, 4756]);
let remove_member_eventids = dynamic([4729, 4733, 4757]);
SecurityEvent
| where isnotempty(TargetSid)
// Remove local group events where the domain of the actor account is not the expected tenant domain
| where not(EventID in (4732, 4733) and SubjectDomainName !in (_ExpectedADGroupDomains))
// Remove group enumeration events where the domain of the actor account is not the expected tenant domain
| where not(EventID in (4799) and SubjectDomainName !in (_ExpectedADGroupDomains))
// Remove local group event where account "NT AUTHORITY\NETWORK SERVICE" is added to "IIS_IUSRS" group
| where not(EventID in (4732, 4733) and MemberSid == "S-1-5-20" and TargetSid == "S-1-5-32-568")
| lookup kind=inner _ADGroups on $left.TargetSid == $right.GroupSID
| as _AuxiliarEvents
| join kind=leftanti (
_AuxiliarEvents
| where EventID in (array_concat(add_member_eventids, remove_member_eventids))
| extend EventID = case(
EventID in (4728, 4729), 4737,
EventID in (4732, 4733), 4735,
EventID in (4756, 4757), 4755,
int(null)
)
) on Account, SourceComputerId, EventID, SubjectLogonId, TargetSid
| where not(array_length(ValidEventIDs) > 0 and not(ValidEventIDs has tostring(EventID)))
| where not(array_length(ExcludedEventIDs) > 0 and ExcludedEventIDs has tostring(EventID))
| as _FilteredAuxiliarEvents
| lookup kind=leftouter (
_FilteredAuxiliarEvents
| where EventID in (4735, 4737, 4755)
| project Account, SourceComputerId, EventID, SubjectLogonId, TargetUserName
| join kind=inner (
SecurityEvent
| where EventID == 5136
| project Account, SourceComputerId, SubjectLogonId, OperationType, EventData
| mv-apply Auxiliar = parse_xml(EventData)["EventData"]["Data"] on (
summarize BagToUnpack = make_bag(pack(tostring(Auxiliar["@Name"]), tostring(Auxiliar["#text"])))
)
| evaluate bag_unpack(BagToUnpack, columnsConflict="keep_source") : (Account:string, SourceComputerId:string, SubjectLogonId:string, OperationType:string, EventData:string, OpCorrelationID:string, ObjectDN:string, ObjectGUID:string, AttributeLDAPDisplayName:string, AttributeValue:string)
| extend TargetUserName = extract(@"(?i:CN\=)([^,]+)", 1, ObjectDN)
) on Account, SourceComputerId, SubjectLogonId, TargetUserName
| extend SplitAttributeValue = iff(AttributeLDAPDisplayName =~ "nTSecurityDescriptor", split(AttributeValue, "("), pack_array(AttributeValue))
| summarize
ValueAdded = take_anyif(SplitAttributeValue, OperationType == "%%14674"),
ValueDeleted = take_anyif(SplitAttributeValue, OperationType == "%%14675"),
AttributeValue_EventData = take_anyif(EventData, OperationType == "%%14674"),
take_any(Account, SourceComputerId, EventID, SubjectLogonId, TargetUserName, AttributeLDAPDisplayName)
by OpCorrelationID, ObjectGUID
| extend
ValueAdded = set_difference(ValueAdded, ValueDeleted),
ValueDeleted = set_difference(ValueDeleted, ValueAdded)
| mv-expand OperationTypeTranslated = pack_array("ValueAdded", "ValueDeleted") to typeof(string), ModifiedAttributeValue = pack_array(ValueAdded, ValueDeleted) to typeof(dynamic)
| mv-expand ModifiedAttributeValue to typeof(string)
| where isnotempty(ModifiedAttributeValue)
| project Account, SourceComputerId, EventID, SubjectLogonId, TargetUserName, OperationTypeTranslated, AttributeLDAPDisplayName, ModifiedAttributeValue, AttributeValue_EventData
) on Account, SourceComputerId, EventID, SubjectLogonId, TargetUserName
| extend
Activity = trim_end(@"\.", Activity),
ActorAccount = SubjectAccount,
GroupSid = TargetSid
// Remove expected activity
| join kind=leftanti _ExpectedActivityADGroups on Activity, ActorAccount, GroupSid
| extend
AttributeValue_MemberSid = extract(@"([^;]+)\)\s*$", 1, ModifiedAttributeValue),
AttributeValue_MemberName = extract(@"^((?i:CN\=.*))$", 1, ModifiedAttributeValue),
ModifiedAttributeValue = trim_end(@"\)\s*$", ModifiedAttributeValue)
| extend
MemberAccount = case(
MemberSid == SubjectUserSid, SubjectUserName,
isnotempty(MemberName), extract(@"(?i:CN\=)([^,]+)", 1, MemberName),
isnotempty(AttributeValue_MemberName), extract(@"(?i:CN\=)([^,]+)", 1, AttributeValue_MemberName),
""
),
MemberSid = case(
isnotempty(MemberSid), MemberSid,
isnotempty(AttributeValue_MemberSid), AttributeValue_MemberSid,
MemberSid
),
GroupName = SAMAccountName,
ActorSid = SubjectUserSid,
ActorAccountType = AccountType,
ActorDomainName = SubjectDomainName,
AlertSeverity = case(
IsWorkingTime(TimeGenerated) and ActorAccount in (_AccountOperators), "Informational",
Severity
)
| project
TimeGenerated,
Computer,
ActorAccount,
Activity,
GroupName,
MemberAccount,
ActorSid,
GroupSid,
MemberSid,
ActorAccountType,
ActorDomainName,
AlertSeverity,
Auditors,
EventData,
AttributeValue_EventData,
AttributeLDAPDisplayName,
OperationTypeTranslated,
ModifiedAttributeValueExplanation
This KQL query is designed to monitor and analyze Active Directory (AD) group activities by processing security events and comparing them against predefined watchlists. Here's a simplified breakdown of what the query does:
-
Load Watchlists:
- It retrieves two watchlists: one for auditing AD group activities (
SID-AuditADObjects) and another for expected significant activities (Activity-ExpectedSignificantActivity). - It processes these watchlists to extract relevant information such as group SIDs, expected domains, and account operators.
- It retrieves two watchlists: one for auditing AD group activities (
-
Filter Security Events:
- It filters security events to exclude certain local group events and group enumeration events based on domain criteria.
- It also excludes specific events like adding "NT AUTHORITY\NETWORK SERVICE" to the "IIS_IUSRS" group.
-
Match and Filter Events:
- It matches the filtered security events with the AD group watchlist to identify relevant group activities.
- It further filters out events that are not valid or are explicitly excluded based on the watchlist criteria.
-
Process Attribute Changes:
- It processes events related to attribute changes (e.g.,
EventID 5136) to identify modifications in group memberships. - It extracts and processes details about added or deleted values in security descriptors.
- It processes events related to attribute changes (e.g.,
-
Exclude Expected Activities:
- It removes activities that are expected and predefined in the expected activity watchlist.
-
Extract and Format Information:
- It extracts detailed information about the actor, group, and member involved in the activity.
- It formats and organizes this information, including actor account details, group names, and modified attribute values.
-
Determine Alert Severity:
- It assigns an alert severity level based on working hours and whether the actor is an account operator.
-
Output:
- The final output includes a structured list of relevant AD group activities with details such as time, computer, actor account, activity type, group name, member account, and alert severity.
In essence, this query helps in monitoring and auditing AD group activities by filtering and analyzing security events against predefined criteria and watchlists, providing insights into potentially significant or unauthorized changes.
Details

Jose Sebastián Canós
Released: March 18, 2024
Tables
_GetWatchlistSecurityEvent
Keywords
SecurityEventAccountGroupActivityDomainMemberNameSidSeverityTimeComputerAuditorsAttributeOperationType
Operators
lethasprojectmv-applysplittypeofsummarizetake_anymake_list_ifisnotemptynotstartswithreplace_stringtoscalarindynamicwherelookupkindonasjoinextendcasearray_concatarray_lengthiffparse_xmlevaluatebag_unpackpackmake_bagpack_arrayset_differencemv-expandtrim_endextracttake_anyifbyto