Query Details

Security Event Suspicious AD Attributes Accessed From Unexpected Source

Query

let query_frequency = 1h;
let query_period = 14d;
let object_threshold = 5;
let _Dictionary =
    _GetWatchlist("Value-WindowsEventsDictionary")
    | project Value = tostring(Value), Translation
;
let _ExpectedAccounts = toscalar(
    _GetWatchlist("Activity-ExpectedSignificantActivity")
    | where Activity in ("AccountOperator", "ADObjectAttributeAccess") and not(Notes has "[Group]")
    | summarize make_list(ActorPrincipalName)
);
let _ExpectedGroups = toscalar(
    _GetWatchlist("Activity-ExpectedSignificantActivity")
    | where Activity == "ADObjectAttributeAccess" and Notes has "[Group]"
    | summarize make_list(Auxiliar)
);
let _Identities =
    IdentityInfo
    | where TimeGenerated > ago(query_period) and isnotempty(AccountDomain)
    | summarize arg_max(TimeGenerated, *) by AccountObjectId, AccountSID
    | project
        SubjectUserName = coalesce(extract(@"(?i:CN\=)([^,]+)", 1, OnPremisesDistinguishedName), AccountName),
        GroupMembership = array_sort_asc(GroupMembership)
;
SecurityEvent
| where TimeGenerated > ago(2 * query_frequency)
| where EventID == 4662
    and (AccessMask in ("0x100", "0x10") or AccessList in ("%%7688", "%%7684") or Properties has_any ("%%7688", "%%7684"))
    and not(Account in (_ExpectedAccounts))
    and (Properties has_all (
            "612cb747-c0e8-4f92-9221-fdd5f15b550d", // unixUserPassword
            "6617e4ac-a2f1-43ab-b60c-11fbd1facf05", // ms-PKI-RoamingTimeStamp - Indicates the time of the last synchronization
            "b3f93023-9239-4f7c-b99c-6745d87adbc2", // ms-PKI-DPAPIMasterKeys - Stores the DPAPI Master Keys. These symmetric keys encrypt the private keys and are themselves encrypted.
            "b7ff5a38-0818-42b0-8110-d3d154c97f24", // ms-PKI-Credential-Roaming-Tokens
            "b8dfa744-31dc-4ef1-ac7c-84baf7ef9da7"  // ms-PKI-AccountCredentials - Stores certificates, certificate signing requests, private keys and saved passwords.
            )
        or Properties has_all (
            "612cb747-c0e8-4f92-9221-fdd5f15b550d", // unixUserPassword
            "bf967a9c-0de6-11d0-a285-00aa003049e2"  // Group
            )
        or Properties has_any (
            "7b8b558a-93a5-4af7-adca-c017e67f1057", // ms-DS-Group-Managed-Service-Account
            "ea715d30-8f53-40d0-bd1e-6109186d782c", // ms-FVE-RecoveryInformation
            "43061ac1-c8ad-4ccc-b785-2bfac20fc60a", // ms-FVE-RecoveryPassword
            "800d94d7-b7a1-42a1-b14d-7cae1423d07f", // ms-DS-Allowed-To-Delegate-To
            "3f78c3e5-f79a-46bd-a0b8-9d18116ddc79"  // ms-DS-Allowed-To-Act-On-Behalf-Of-Other-Identity
        ))
| summarize
    StartTime = min(TimeGenerated),
    EndTime = max(TimeGenerated),
    EventCount = count(),
    ObjectNameCount = count_distinct(ObjectName),
    ObjectNamesSample = array_sort_asc(make_set_if(trim(@"[\%\{\}\s]+", ObjectName), isnotempty(ObjectName), 20)),
    SubjectLogonId = make_set_if(SubjectLogonId, isnotempty(SubjectLogonId), 20),
    Properties = make_set_if(Properties, isnotempty(Properties), 50),
    take_any(Activity, OperationType, AccountType, SubjectUserName, AccessList, EventOriginId)
    by Computer, Account, AccessMask, ObjectType
| where ObjectNameCount > object_threshold
| where not(ObjectNameCount < 100 and ObjectType has_any ("bf967aba-0de6-11d0-a285-00aa003049e2", "bf967a86-0de6-11d0-a285-00aa003049e2")) // User Computer
| lookup kind=leftouter _Identities on SubjectUserName
| project-away SubjectUserName
| where not(array_length(_ExpectedGroups) > 0 and GroupMembership has_all (_ExpectedGroups))
| extend AccessList = trim(@"\s+", AccessList)
| lookup kind=leftouter (
    _Dictionary
    | project
        AccessList = Value,
        AccessListTranslated = Translation
    ) on AccessList
| extend ObjectType = trim(@"[\%\{\}\s]+", ObjectType)
| lookup kind=leftouter (
    _Dictionary
    | project
        ObjectType = Value,
        ObjectTypeTranslated = Translation
    ) on ObjectType
| mv-expand Properties to typeof(string)
| mv-expand PropertiesKey = split(translate("{}", "", trim(@"\s+", replace_regex(Properties, @"\s+", " "))), " ") to typeof(string)
| lookup kind=leftouter (
    _Dictionary
    | project
        PropertiesKey = Value,
        PropertiesKeyTranslated = Translation
    )
    on PropertiesKey
| summarize
    take_any(*),
    PropertiesTranslated = strcat_array(make_list(iff(isnotempty(PropertiesKeyTranslated), PropertiesKeyTranslated, PropertiesKey)), " | ")
    by EventOriginId
| as hint.materialized=true _Auxiliar
| mv-expand SubjectLogonId to typeof(string)
| lookup kind=leftouter (
    SecurityEvent
    | where TimeGenerated > ago(query_frequency + 1h)
    | where EventID == 4624 and Account in (toscalar(_Auxiliar | summarize make_list(Account)))
    | project Computer, Account, TargetLogonId, IpAddress
    )
    on Computer, Account, $left.SubjectLogonId == $right.TargetLogonId
| summarize
    SubjectLogonIds = make_set(SubjectLogonId),
    IpAddresses = make_set_if(IpAddress, isnotempty(IpAddress)),
    take_any(*)
    by EventOriginId
| summarize
    StartTime = min(StartTime),
    EndTime = max(EndTime),
    Computers = make_set(Computer),
    SubjectLogonIds = make_set(SubjectLogonIds),
    IpAddresses = make_set(IpAddress),
    EventCount = sum(EventCount),
    AccessLists = make_set(AccessList),
    AccessListsTranslated = make_set(AccessListTranslated),
    ObjectTypes = make_set(ObjectType),
    ObjectTypesTranslated = array_sort_asc(make_set(ObjectTypeTranslated)),
    ObjectNameCount = sum(ObjectNameCount),
    ObjectNamesSample = make_set(ObjectNamesSample),
    Properties = make_set(Properties),
    PropertiesTranslated = make_set(PropertiesTranslated),
    take_any(AccountType, Activity, OperationType, GroupMembership)
    by Account
| where EndTime > ago(query_frequency)
| where not(array_length(ObjectTypes) == 1 and ObjectTypes has_any ("bf967aba-0de6-11d0-a285-00aa003049e2", "bf967a86-0de6-11d0-a285-00aa003049e2", "bf967a9c-0de6-11d0-a285-00aa003049e2")) // User Computer Group
| project
    StartTime,
    EndTime,
    Computers,
    Account,
    AccountType,
    SubjectLogonIds,
    IpAddress = tostring(IpAddresses[0]),
    IpAddresses,
    Activity,
    EventCount,
    OperationType,
    AccessLists,
    AccessListsTranslated,
    ObjectTypes,
    ObjectTypesTranslated,
    ObjectNameCount,
    ObjectNameEventCountRatio = round(ObjectNameCount / toreal(EventCount), 2),
    ObjectNamesSample,
    Properties,
    PropertiesTranslated,
    AccountGroupMembership = GroupMembership

Explanation

This KQL (Kusto Query Language) query is designed to analyze security events, specifically looking for unusual or potentially suspicious activities related to Active Directory object access. Here's a simplified breakdown of what the query does:

  1. Setup and Configuration:

    • It defines some parameters like query_frequency, query_period, and object_threshold to control the time frame and thresholds for the analysis.
    • It retrieves data from watchlists to identify expected accounts and groups that are allowed to perform significant activities.
  2. Data Preparation:

    • It gathers identity information from the IdentityInfo table to map user accounts and their group memberships.
    • It creates a dictionary from a watchlist to translate certain event values into more understandable terms.
  3. Event Filtering:

    • The query focuses on security events with EventID 4662, which indicates an operation was performed on an Active Directory object.
    • It filters out events involving expected accounts and checks for specific access properties that might indicate sensitive operations.
  4. Event Aggregation:

    • It summarizes the events by counting occurrences, distinct object names, and other attributes.
    • It filters out events that do not meet certain criteria, such as having too few distinct object names.
  5. Data Enrichment:

    • It enriches the data by looking up additional information about user identities and translating technical codes into human-readable terms using the dictionary.
  6. Final Aggregation and Output:

    • The query aggregates the results by account, summarizing various attributes like computers involved, IP addresses, access lists, object types, and properties.
    • It calculates a ratio of distinct object names to event count to help identify unusual patterns.
    • Finally, it projects the relevant fields for output, providing a concise summary of potentially suspicious activities.

In essence, this query is designed to detect and summarize unusual access patterns to Active Directory objects, helping security analysts identify potential security incidents.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: December 3, 2025

Tables

_GetWatchlistIdentityInfoSecurityEvent

Keywords

SecurityEventIdentityInfoAccountComputerGroupMembershipPropertiesActivityOperationTypeEventIDAccessMaskObjectTypeEventOriginIdLogonIdIpAddress

Operators

letprojecttostringtoscalarwhereinnothassummarizemake_listarg_maxcoalesceextractagoisnotemptyarray_sort_aschas_anyhas_allcountcount_distinctmake_set_iftrimtake_anybylookupkindleftouterproject-awayextendreplace_regexmv-expandsplitiffstrcat_arrayashint.materializedtotypeofmake_setarray_lengthroundtoreal

Actions

GitHub