Query Details

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,
    ModifiedAttributeValue

Explanation

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Exclude Expected Activities:

    • It removes activities that are expected and predefined in the expected activity watchlist.
  6. 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.
  7. Determine Alert Severity:

    • It assigns an alert severity level based on working hours and whether the actor is an account operator.
  8. 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 profile picture

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

Actions

GitHub