diff --git a/filters/audits/windows.md b/filters/audits/windows.md new file mode 100644 index 000000000..52b9fe61d --- /dev/null +++ b/filters/audits/windows.md @@ -0,0 +1,128 @@ +# Windows normalization and correlation review + +This draft targets UTMStack `v11`. The SDK Event/Side protobuf in the pinned +`go-sdk v1.1.31` is the schema authority. The local dictionary is supporting +material, not an alternative schema. + +## Correction after review + +The first draft removed placeholder IPs after promoting them to `origin.ip`, +while authentication rules still required `{{.origin.ip}}` in historical +searches. The SDK returns an error when a placeholder is missing; it returns +before evaluating an `or` fallback. Restoring `-` as a standardized IP would +also aggregate unrelated sources under one placeholder. + +The filter now validates `log.data.IpAddress` at the original rename. Invalid, +missing and unspecified addresses stay in their original vendor field and +never enter `origin.ip`. The unspecified-address guard uses CIDR membership, +not literal strings: expanded/compressed IPv6 zero and IPv4-mapped zero spellings +are rejected while the exact original vendor value is preserved. Tests cover +account and workstation fallback, no qualified fallback, and a valid mapped-IPv4 +control. These alternate-spelling regressions are synthetic; no additional +customer occurrence is claimed. Authentication correlation chooses a real source IP, +then workstation, then actor account, recorded in `log.authenticationSource` +and `log.authenticationSourceType`. The account fallback identifies an account, +not a network client. Every affected history search scopes that identity by +its kind, domain scope, the agent's `dataSource`, and event code. Brute-force rules also +require the target account; Kerberos searches constrain ticket encryption and, +for AS-REP, preauthentication type. No shared placeholder is an identity. A username-only fallback requires its +domain or an already qualified UPN; an unqualified username without a domain +is not treated as a safe correlation identity. + +The native Windows collector sets `dataSource` from its hostname for every +record. The rules reject its documented `unknown`/empty sentinel so account +fallback cannot pool unidentified collectors. They do not require a redundant +`exists(dataSource)` check. The filter and all seven correlation consumers +must be deployed together. Existing indexed records do not have the new +correlation fields, so the updated history windows warm up after deployment. + +Golden Ticket's historical query previously depended on `origin.host`, not +`origin.ip`; native Kerberos records can lack that workstation too. Its +correlation now uses the same identity selection. Filter-derived `log.authenticationCandidate.*` markers repeat each exact +trigger predicate so benign events with the same event code cannot satisfy the +historical threshold. The tests assert marker/predicate parity. Success after +failures searches the failed-logon marker. The update preserves each rule's +existing count and time window. It does not claim that the existing +Golden/Silver Ticket heuristics prove forged tickets. + +## Standard field promotion + +The native agent emits `computer`, `timestamp`/`timeCreated`, and `data.IpPort`. +The filter promotes the event-producing computer to `target.host`, device +time to `deviceTime`, and valid remote ports to `origin.port`. WorkstationName +and the native NTLM `Workstation` alias describe `origin.host`; the recording computer must not overwrite +the remote workstation. Kerberos account names are also promoted to +`origin.user` while the existing `target.user` and vendor aliases remain +available to consumers. Vendor data without a standard counterpart remains +under `log`. `SubjectDomainName` and the authenticated account's domain are +promoted without mixing actor and target roles. A non-placeholder `ProcessName` +is copied to `origin.path`, preserving the vendor alias used by current rules. + +NTLM status must also accept the agent's numeric zero: CEL `regexMatch` only +matches strings. The first draft's string-only check would have changed a +successful native 4776 event to failure. The revised check covers numeric zero +and hexadecimal zero strings, with nonzero numeric/string failure regressions. + +The original fixes for successful account administration, NTLM status, +placeholder cleanup, and event-versus-alert grouping remain included. + +## Validation and limits + +- `windows_contract_test.go` is standalone and runs with `go test ./...` in + `plugins/alerts`, without the shared test-runner PR. +- 60 sanitized raw JSON fixtures exercise valid IPv4/IPv6, missing/placeholder + addresses, host/account fallback, valid/invalid ports, host roles and time. +- 50 positive predicate cases cover all seven changed correlation consumers. + Negative identity cases also compile/evaluate all 38 shipped Windows rules. +- The real SDK executes historical requests against a local mock OpenSearch + server, including mapping resolution, placeholder expansion, query creation, + time/count boundaries and separation of different sources, identity kinds, + collectors, account domains, event types and non-candidate history. The old missing-IP regression is reproduced with + the SDK, without a customer connection. +- The shared manifest adds seven nonempty rule assertions to its normalization + cases. Its runner is supplied by draft #2590. + +JSON extraction and filter transformations are an offline model based on the +SDK sanitization utility and documented filter operations. These tests do not +execute the closed EventProcessor, a live OpenSearch cluster, alert creation +or delivery. Staging must compare real raw events, normalized results and +created alerts before rollout. No customer configuration was changed. + +## Bounded live evidence + +The deployed Windows filter was also read without modification. Its relevant +mappings matched the repository baseline: unguarded IpAddress promotion, +WorkstationName alone, no computer/port/process-path promotion, and numeric-zero +NTLM status handled through `equals`. Configuration SHA-256: +`c3a781cc3f2015c3b9e1cb66773da3463186ccec68518051fcf3be533f77cc1c`. + +A read-only seven-day sample from three instances yielded 28 distinct records +for the requested authentication event codes. Fifteen had no usable source IP. +All 28 retained the native computer only under `log`, and all 28 had deviceTime +defaulted to ingestion time instead of the raw vendor timestamp. Three NTLM +records supplied `data.Workstation` without a standardized origin host; nine +records supplied a process path without `origin.path`. The private evidence +pack keeps the instance/document anchors without publishing customer payloads. + +No missing-IP 4768/4769/4771 events were observed in this seven-day aggregate. +The report's broad claim that Kerberos placeholder IPs were observed is not +supported by this sample. The SDK's missing-placeholder behavior is reproduced +with synthetic Kerberos records; actual missing/placeholder IPs are confirmed +for local logon and credential-validation event types. + +Replaying those 28 raw records through the offline filter model and actual CEL +produced two failed-logon candidates and five successful-logon candidates with +resolved history placeholders. This is not proof of historical thresholds or +created alerts: the sample is intentionally bounded and the live filter/rules +were not replaced. + +## Sources + +- [SDK schema](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/plugins.proto) +- [SDK correlation execution](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/rules.go) +- [Native Windows collector](https://github.com/utmstack/UTMStack/blob/v11/agent/collector/platform/windows_amd64.go) +- [Filter operations](https://github.com/threatwinds/go-sdk/wiki/Filter-Steps-Reference) +- [Standard event schema](https://github.com/threatwinds/go-sdk/wiki/Standard-Event-Schema) + +Alert grouping for `lastEvent.*` still depends on the shared alert-grouping +fix in #2590, which requires its own staging comparison of alert counts. diff --git a/filters/windows/windows-events.yml b/filters/windows/windows-events.yml index 1cea949aa..1e546dd7b 100644 --- a/filters/windows/windows-events.yml +++ b/filters/windows/windows-events.yml @@ -1,4 +1,4 @@ -# Windows_Agent filter, version 3.1.1 +# Windows_Agent filter, version 3.1.2 # Based on winlogbeat fields, reference [8.15] # See https://www.elastic.co/guide/en/beats/winlogbeat/current/exported-fields-winlog.html @@ -9,6 +9,15 @@ pipeline: - json: source: raw + # Native agent and Winlogbeat aliases. The event-producing computer is the + # target host; it must not overwrite the remote WorkstationName origin. + - rename: + from: [log.computer, log.computername, log.computer_name] + to: target.host + - rename: + from: [log.timestamp, log.timeCreated] + to: deviceTime + # Renaming useful fields - rename: from: @@ -134,6 +143,7 @@ pipeline: from: - log.data.IpAddress to: origin.ip + where: (inCIDR("log.data.IpAddress", "0.0.0.0/0") || inCIDR("log.data.IpAddress", "::/0")) && !inCIDR("log.data.IpAddress", "0.0.0.0/32") && !inCIDR("log.data.IpAddress", "::/128") - rename: from: @@ -252,6 +262,7 @@ pipeline: - rename: from: - log.data.WorkstationName + - log.data.Workstation to: origin.host - rename: @@ -3037,29 +3048,29 @@ pipeline: value: "success" where: 'oneOf("log.eventCode", [4624, 4648, 4672, 4720, 4722, 4728, 4732, 4756, 4767])' - add: - function: 'string' + function: string params: key: actionResult - value: "blocked" - where: 'oneOf("log.eventCode", [4725, 4726, 4740])' + value: success + where: oneOf("log.eventCode", [4725, 4726, 4740]) - add: - function: 'string' + function: string params: key: actionResult - value: "failed" - where: 'oneOf("log.eventCode", [4625, 4771])' + value: failure + where: oneOf("log.eventCode", [4625, 4771]) - add: - function: 'string' + function: string params: key: actionResult - value: "success" - where: 'equals("log.eventCode", 4776) && equals("log.eventDataStatus", "0")' + value: success + where: equals("log.eventCode", 4776) && (equals("log.eventDataStatus", 0) || regexMatch("log.eventDataStatus", "(?i)^(0|0x0+)$")) - add: - function: 'string' + function: string params: key: actionResult - value: "failed" - where: 'equals("log.eventCode", 4776) && exists("log.eventDataStatus") && !equals("log.eventDataStatus", "0")' + value: failure + where: equals("log.eventCode", 4776) && exists("log.eventDataStatus") && !(equals("log.eventDataStatus", 0) || regexMatch("log.eventDataStatus", "(?i)^(0|0x0+)$")) - delete: fields: @@ -3070,4 +3081,266 @@ pipeline: - log.metadata - log.event - log.ecs - - log.log \ No newline at end of file + - log.log + + # Promote standard fields while retaining vendor fields used by rules. + - grok: + source: log.eventDataSubjectUserName + patterns: + - fieldName: origin.user + pattern: '{{.greedy}}' + where: exists("log.eventDataSubjectUserName") && !oneOf("log.eventDataSubjectUserName",["-",""]) + - grok: + source: target.user + patterns: + - fieldName: origin.user + pattern: '{{.greedy}}' + where: oneOf("log.eventCode",[4624,4625,4634,4647,4768,4769,4770,4771,4776]) && exists("target.user") && !oneOf("target.user",["-",""]) + + # Normalize source fields to the standard event schema. + - delete: + fields: + - origin.user + where: oneOf("origin.user",["-",""]) + - delete: + fields: + - target.user + where: oneOf("target.user",["-",""]) + - delete: + fields: + - origin.host + where: oneOf("origin.host",["-",""]) + - delete: + fields: + - target.host + where: oneOf("target.host",["-",""]) + - delete: + fields: + - origin.domain + where: oneOf("origin.domain",["-",""]) + - delete: + fields: + - target.domain + where: oneOf("target.domain",["-",""]) + + # Preserve the actor's domain and process path in standard fields too. + # Logon/Kerberos TargetUserName is the authenticated account; its domain + # must not be taken from an unrelated SubjectUserName service account. + - grok: + source: log.eventDataSubjectDomainName + patterns: + - fieldName: origin.domain + pattern: '{{.greedy}}' + where: exists("log.eventDataSubjectDomainName") && !oneOf("log.eventDataSubjectDomainName", ["-", ""]) && (!oneOf("log.eventCode", [4624,4625,4634,4647,4768,4769,4770,4771,4776]) || !exists("target.user")) + - grok: + source: target.domain + patterns: + - fieldName: origin.domain + pattern: '{{.greedy}}' + where: oneOf("log.eventCode", [4624,4625,4634,4647,4768,4769,4770,4771,4776]) && exists("target.user") && exists("target.domain") + - grok: + source: log.eventDataProcessName + patterns: + - fieldName: origin.path + pattern: '{{.greedy}}' + where: exists("log.eventDataProcessName") && !oneOf("log.eventDataProcessName", ["-", ""]) + + # IpPort is the remote authentication port, not an event status code. + # Keep the vendor alias for existing searches and only promote real ports. + - cast: + fields: [log.eventDataIpPort] + to: string + - grok: + source: log.eventDataIpPort + patterns: + - fieldName: origin.port + pattern: '{{.greedy}}' + where: regexMatch("log.eventDataIpPort", "^[0-9]+$") && greaterThan("log.eventDataIpPort", 0) && lessOrEqual("log.eventDataIpPort", 65535) + - cast: + fields: [origin.port] + to: int + + # Correlation requires a real identity, never the shared '-' placeholder. + # Prefer the network source, then workstation, then authenticated account. + # Rules also scope this derived value by its kind and by dataSource. + - delete: + fields: [log.authenticationSource, log.authenticationSourceType, log.authenticationSourceDomain, log.authenticationCandidate] + - grok: + source: origin.ip + patterns: + - fieldName: log.authenticationSource + pattern: '{{.greedy}}' + where: exists("origin.ip") + - add: + function: string + params: + key: log.authenticationSourceType + value: ip + where: exists("log.authenticationSource") + - grok: + source: origin.host + patterns: + - fieldName: log.authenticationSource + pattern: '{{.greedy}}' + where: >- + !exists("log.authenticationSource") && exists("origin.host") + - add: + function: string + params: + key: log.authenticationSourceType + value: host + where: exists("log.authenticationSource") && !exists("log.authenticationSourceType") + - grok: + source: origin.user + patterns: + - fieldName: log.authenticationSource + pattern: '{{.greedy}}' + where: >- + !exists("log.authenticationSource") && exists("origin.user") && (exists("origin.domain") || regexMatch("origin.user", "^[^@]+@[^@]+$")) + - add: + function: string + params: + key: log.authenticationSourceType + value: user + where: exists("log.authenticationSource") && !exists("log.authenticationSourceType") + + # User-only identities require a domain or an already qualified UPN. The + # explicit scope prevents equal usernames from different domains joining. + - grok: + source: origin.domain + patterns: + - fieldName: log.authenticationSourceDomain + pattern: '{{.greedy}}' + where: equals("log.authenticationSourceType", "user") && exists("origin.domain") + - add: + function: string + params: + key: log.authenticationSourceDomain + value: qualified-upn + where: equals("log.authenticationSourceType", "user") && !exists("log.authenticationSourceDomain") + - add: + function: string + params: + key: log.authenticationSourceDomain + value: network-source + where: oneOf("log.authenticationSourceType", ["ip", "host"]) + + # Historical searches support exact terms, not a CEL predicate. Mark the + # same candidates tested by these authentication rules so routine events + # with the same event ID cannot fill an attack threshold. Keep predicates + # and markers together; the raw/CEL regression checks assert their parity. + - add: + function: string + params: + key: log.authenticationCandidate.kerberoastingDetection + value: match + where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( + equals("log.eventCode", "4769") && + equals("log.channel", "Security") && + equals("log.eventDataTicketEncryptionType", "23") && + !regexMatch("log.eventDataServiceName", "(?i)\\$$") && + !equals("log.eventDataServiceName", "krbtgt") && + !oneOf("log.eventDataTicketOptions", ["1082195968", "1082130432", "1082130432"]) && + exists("log.eventDataServiceName") + ) + - add: + function: string + params: + key: log.authenticationCandidate.asrepRoastingDetection + value: match + where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( + equals("log.eventCode", "4768") && + equals("log.channel", "Security") && + equals("log.eventDataTicketEncryptionType", "23") && + equals("log.eventDataPreAuthType", "0") && + !regexMatch("target.user", "(?i)\\$$") && + exists("target.user") + ) + - add: + function: string + params: + key: log.authenticationCandidate.silverTicketDetection + value: match + where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( + equals("log.eventCode", "4769") && + equals("log.channel", "Security") && + ( + equals("log.eventDataTicketEncryptionType", "23") && + !regexMatch("log.eventDataServiceName", "(?i)(krbtgt|\\$$)") && + !oneOf("log.eventDataStatus", ["0", "6"]) && + exists("log.authenticationSource") + ) + ) + - add: + function: string + params: + key: log.authenticationCandidate.goldenTicketDetection + value: match + where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( + ( + equals("log.eventCode", "4769") && + equals("log.channel", "Security") && + equals("log.eventDataServiceName", "krbtgt") && + !equals("log.eventDataStatus", "0") && + exists("log.authenticationSource") + ) || + ( + equals("log.eventCode", "4768") && + equals("log.channel", "Security") && + !oneOf("log.eventDataTicketEncryptionType", ["18", "17"]) && + exists("target.user") && + !regexMatch("target.user", "(?i)\\$$") + ) || + ( + equals("log.eventCode", "4672") && + equals("log.channel", "Security") && + contains("log.eventDataPrivilegeList", "SeTcbPrivilege") && + !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE)$") && + !regexMatch("log.eventDataSubjectUserName", "(?i)\\$$") + ) + ) + - add: + function: string + params: + key: log.authenticationCandidate.adfsAuthenticationAnomalies + value: match + where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( + equals("log.providerName", "AD FS") && (equals("log.eventCode", "342") || equals("log.eventCode", "516")) && contains("log.message", "token validation failed") + ) + - add: + function: string + params: + key: log.authenticationCandidate.bruteforceAttack + value: match + where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && exists("target.user") && + ( + equals("log.eventCode", 4625) + ) + - add: + function: string + params: + key: log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess + value: match + where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && exists("target.user") && + ( + equals("log.eventCode", 4624) + ) diff --git a/plugins/alerts/testdata/filter-contracts/windows.json b/plugins/alerts/testdata/filter-contracts/windows.json new file mode 100644 index 000000000..82040f70d --- /dev/null +++ b/plugins/alerts/testdata/filter-contracts/windows.json @@ -0,0 +1,483 @@ +{ + "technology": "Windows", + "filters": [ + "filters/windows/windows-events.yml" + ], + "rules": [ + "rules/windows/adfs_authentication_anomalies.yml", + "rules/windows/adminsdholder_abuse.yml", + "rules/windows/asrep_roasting_detection.yml", + "rules/windows/audit_log_was_cleared.yml", + "rules/windows/audit_or_event_log_tampering.yml", + "rules/windows/bruteforce_attack.yml", + "rules/windows/bruteforce_multiple_logon_failure_followed_by_success.yml", + "rules/windows/certificate_services_abuse.yml", + "rules/windows/credential_dumping_tool_indicators.yml", + "rules/windows/golden_ticket_detection.yml", + "rules/windows/kerberoasting_detection.yml", + "rules/windows/lolbin_proxy_execution.yml", + "rules/windows/lsass_memdump_handle_access.yml", + "rules/windows/masquerading_detection.yml", + "rules/windows/ntds_extraction_attempts.yml", + "rules/windows/ntlm_downgrade_attack.yml", + "rules/windows/office_application_spawned_shell.yml", + "rules/windows/possible_exploit_over_reverse_tunneling_using_stolen_credentials.yml", + "rules/windows/possible_remote_code_execution_using_printernightmare.yml", + "rules/windows/powershell_profiles.yml", + "rules/windows/printspooler_service_suspicious_file.yml", + "rules/windows/printspooler_suspicious_spl_file.yml", + "rules/windows/ransom_inhibit_system_recovery.yml", + "rules/windows/ransom_multiple_file_deletion.yml", + "rules/windows/ransom_note_creation.yml", + "rules/windows/ransom_unusual_file_extension.yml", + "rules/windows/remote_file_copy_desktopimgdownldr.yml", + "rules/windows/sam_database_access.yml", + "rules/windows/sid_history_injection.yml", + "rules/windows/silver_ticket_detection.yml", + "rules/windows/smbv1_usage_detection.yml", + "rules/windows/suspicious_event_as_the_binary_may_have_been_dropped_using_windows_dmin_shares.yml", + "rules/windows/suspicious_powershell_obfuscation.yml", + "rules/windows/suspicious_scheduled_task_created.yml", + "rules/windows/uac_bypass_dll_sideloading.yml", + "rules/windows/unusual_dns_service_file_writes.yml", + "rules/windows/unusual_process_network_connection.yml", + "rules/windows/windows_remote_management_abuse.yml" + ], + "fixtures": [ + { + "name": "Windows account operation 4725", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "eventCode": 4725 + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Windows account operation 4726", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "eventCode": 4726 + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Windows account operation 4740", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "eventCode": 4740 + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Windows NTLM status 0", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "eventCode": 4776, + "data": { + "Status": "0" + } + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Windows NTLM status 0x0", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "eventCode": 4776, + "data": { + "Status": "0x0" + } + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Windows NTLM status 0x00000000", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "eventCode": 4776, + "data": { + "Status": "0x00000000" + } + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Windows NTLM status 0xC000006A", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "eventCode": 4776, + "data": { + "Status": "0xC000006A" + } + } + }, + "expected": { + "actionResult": "failure" + }, + "absent": [], + "rules": {} + }, + { + "name": "Windows missing IP placeholder", + "filter": "windows/windows-events.yml", + "input": { + "log": { + "data": { + "IpAddress": "-", + "TargetUserName": "-" + } + } + }, + "expected": {}, + "absent": [ + "origin.ip", + "target.user", + "origin.user" + ], + "rules": {} + }, + { + "name": "kerberoasting_detection dash-with-account", + "filter": "windows/windows-events.yml", + "input": { + "dataType": "wineventlog", + "dataSource": "dc01", + "log": { + "timestamp": "2026-09-17T12:00:00Z", + "providername": "Microsoft-Windows-Security-Auditing", + "providerguid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "recordId": 1234, + "channel": "Security", + "computer": "dc01.example.test", + "data": { + "TicketEncryptionType": 23, + "TicketOptions": 0, + "ServiceName": "sql-service", + "TargetUserName": "alice", + "TargetDomainName": "EXAMPLE", + "IpAddress": "-", + "IpPort": "49152" + }, + "eventCode": 4769 + } + }, + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "rules": { + "rules/windows/kerberoasting_detection.yml": true + } + }, + { + "name": "asrep_roasting_detection dash-with-account", + "filter": "windows/windows-events.yml", + "input": { + "dataType": "wineventlog", + "dataSource": "dc01", + "log": { + "timestamp": "2026-09-17T12:00:00Z", + "providername": "Microsoft-Windows-Security-Auditing", + "providerguid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "recordId": 1234, + "channel": "Security", + "computer": "dc01.example.test", + "data": { + "TicketEncryptionType": 23, + "PreAuthType": "0", + "TargetUserName": "alice", + "TargetDomainName": "EXAMPLE", + "IpAddress": "-", + "IpPort": "49152" + }, + "eventCode": 4768 + } + }, + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.asrepRoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "rules": { + "rules/windows/asrep_roasting_detection.yml": true + } + }, + { + "name": "silver_ticket_detection dash-with-account", + "filter": "windows/windows-events.yml", + "input": { + "dataType": "wineventlog", + "dataSource": "dc01", + "log": { + "timestamp": "2026-09-17T12:00:00Z", + "providername": "Microsoft-Windows-Security-Auditing", + "providerguid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "recordId": 1234, + "channel": "Security", + "computer": "dc01.example.test", + "data": { + "TicketEncryptionType": 23, + "Status": "7", + "ServiceName": "sql-service", + "TargetUserName": "alice", + "TargetDomainName": "EXAMPLE", + "IpAddress": "-", + "IpPort": "49152" + }, + "eventCode": 4769 + } + }, + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.silverTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "rules": { + "rules/windows/silver_ticket_detection.yml": true + } + }, + { + "name": "golden_ticket_detection dash-with-account", + "filter": "windows/windows-events.yml", + "input": { + "dataType": "wineventlog", + "dataSource": "dc01", + "log": { + "timestamp": "2026-09-17T12:00:00Z", + "providername": "Microsoft-Windows-Security-Auditing", + "providerguid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "recordId": 1234, + "channel": "Security", + "computer": "dc01.example.test", + "data": { + "PrivilegeList": "SeTcbPrivilege", + "SubjectUserName": "alice", + "SubjectDomainName": "EXAMPLE", + "IpAddress": "-", + "IpPort": "49152" + }, + "eventCode": 4672 + } + }, + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.goldenTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "rules": { + "rules/windows/golden_ticket_detection.yml": true + } + }, + { + "name": "bruteforce_attack dash-with-account", + "filter": "windows/windows-events.yml", + "input": { + "dataType": "wineventlog", + "dataSource": "dc01", + "log": { + "timestamp": "2026-09-17T12:00:00Z", + "providername": "Microsoft-Windows-Security-Auditing", + "providerguid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "recordId": 1234, + "channel": "Security", + "computer": "dc01.example.test", + "data": { + "TargetUserName": "alice", + "TargetDomainName": "EXAMPLE", + "Status": 3221225578, + "IpAddress": "-", + "IpPort": "49152" + }, + "eventCode": 4625 + } + }, + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "rules": { + "rules/windows/bruteforce_attack.yml": true + } + }, + { + "name": "bruteforce_multiple_logon_failure_followed_by_success dash-with-account", + "filter": "windows/windows-events.yml", + "input": { + "dataType": "wineventlog", + "dataSource": "dc01", + "log": { + "timestamp": "2026-09-17T12:00:00Z", + "providername": "Microsoft-Windows-Security-Auditing", + "providerguid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "recordId": 1234, + "channel": "Security", + "computer": "dc01.example.test", + "data": { + "TargetUserName": "alice", + "TargetDomainName": "EXAMPLE", + "Status": 0, + "IpAddress": "-", + "IpPort": "49152" + }, + "eventCode": 4624 + } + }, + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "rules": { + "rules/windows/bruteforce_multiple_logon_failure_followed_by_success.yml": true + } + }, + { + "name": "adfs_authentication_anomalies dash-with-account", + "filter": "windows/windows-events.yml", + "input": { + "dataType": "wineventlog", + "dataSource": "dc01", + "log": { + "timestamp": "2026-09-17T12:00:00Z", + "providername": "AD FS", + "providerguid": "{54849625-5478-4994-a5ba-3e3b0328c30d}", + "recordId": 1234, + "channel": "Security", + "computer": "dc01.example.test", + "data": { + "SubjectUserName": "alice", + "IpAddress": "-", + "IpPort": "49152", + "SubjectDomainName": "EXAMPLE" + }, + "eventCode": 342, + "message": "token validation failed" + } + }, + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "AD FS", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.adfsAuthenticationAnomalies": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "rules": { + "rules/windows/adfs_authentication_anomalies.yml": true + } + } + ] +} diff --git a/plugins/alerts/testdata/windows_raw.json b/plugins/alerts/testdata/windows_raw.json new file mode 100644 index 000000000..35e6f30ae --- /dev/null +++ b/plugins/alerts/testdata/windows_raw.json @@ -0,0 +1,1356 @@ +[ + { + "name": "kerberoasting_detection ipv4", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"192.0.2.41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "192.0.2.41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "192.0.2.41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "kerberoasting_detection ipv6", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"2001:db8::41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "2001:db8::41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "2001:db8::41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "kerberoasting_detection dash-with-host", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "kerberoasting_detection dash-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "kerberoasting_detection absent-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "kerberoasting_detection unspecified-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"0.0.0.0\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "asrep_roasting_detection ipv4", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"PreAuthType\":\"0\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"192.0.2.41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4768}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "192.0.2.41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "192.0.2.41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.asrepRoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "asrep_roasting_detection" + ] + }, + { + "name": "asrep_roasting_detection ipv6", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"PreAuthType\":\"0\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"2001:db8::41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4768}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "2001:db8::41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "2001:db8::41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.asrepRoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "asrep_roasting_detection" + ] + }, + { + "name": "asrep_roasting_detection dash-with-host", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"PreAuthType\":\"0\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4768}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.asrepRoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "asrep_roasting_detection" + ] + }, + { + "name": "asrep_roasting_detection dash-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"PreAuthType\":\"0\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"IpPort\":\"49152\"},\"eventCode\":4768}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.asrepRoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "asrep_roasting_detection" + ] + }, + { + "name": "asrep_roasting_detection absent-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"PreAuthType\":\"0\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpPort\":\"49152\"},\"eventCode\":4768}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.asrepRoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "asrep_roasting_detection" + ] + }, + { + "name": "asrep_roasting_detection unspecified-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"PreAuthType\":\"0\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"0.0.0.0\",\"IpPort\":\"49152\"},\"eventCode\":4768}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.asrepRoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "asrep_roasting_detection" + ] + }, + { + "name": "silver_ticket_detection ipv4", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"Status\":\"7\",\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"192.0.2.41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "192.0.2.41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "192.0.2.41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.silverTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "silver_ticket_detection" + ] + }, + { + "name": "silver_ticket_detection ipv6", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"Status\":\"7\",\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"2001:db8::41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "2001:db8::41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "2001:db8::41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.silverTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "silver_ticket_detection" + ] + }, + { + "name": "silver_ticket_detection dash-with-host", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"Status\":\"7\",\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.silverTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "silver_ticket_detection" + ] + }, + { + "name": "silver_ticket_detection dash-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"Status\":\"7\",\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.silverTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "silver_ticket_detection" + ] + }, + { + "name": "silver_ticket_detection absent-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"Status\":\"7\",\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.silverTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "silver_ticket_detection" + ] + }, + { + "name": "silver_ticket_detection unspecified-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"Status\":\"7\",\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"0.0.0.0\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.silverTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "silver_ticket_detection" + ] + }, + { + "name": "golden_ticket_detection ipv4", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"PrivilegeList\":\"SeTcbPrivilege\",\"SubjectUserName\":\"alice\",\"SubjectDomainName\":\"EXAMPLE\",\"IpAddress\":\"192.0.2.41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4672}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "192.0.2.41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "192.0.2.41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.goldenTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "golden_ticket_detection" + ] + }, + { + "name": "golden_ticket_detection ipv6", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"PrivilegeList\":\"SeTcbPrivilege\",\"SubjectUserName\":\"alice\",\"SubjectDomainName\":\"EXAMPLE\",\"IpAddress\":\"2001:db8::41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4672}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "2001:db8::41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "2001:db8::41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.goldenTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "golden_ticket_detection" + ] + }, + { + "name": "golden_ticket_detection dash-with-host", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"PrivilegeList\":\"SeTcbPrivilege\",\"SubjectUserName\":\"alice\",\"SubjectDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4672}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.goldenTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "golden_ticket_detection" + ] + }, + { + "name": "golden_ticket_detection dash-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"PrivilegeList\":\"SeTcbPrivilege\",\"SubjectUserName\":\"alice\",\"SubjectDomainName\":\"EXAMPLE\",\"IpAddress\":\"-\",\"IpPort\":\"49152\"},\"eventCode\":4672}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.goldenTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "golden_ticket_detection" + ] + }, + { + "name": "golden_ticket_detection absent-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"PrivilegeList\":\"SeTcbPrivilege\",\"SubjectUserName\":\"alice\",\"SubjectDomainName\":\"EXAMPLE\",\"IpPort\":\"49152\"},\"eventCode\":4672}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.goldenTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "golden_ticket_detection" + ] + }, + { + "name": "golden_ticket_detection unspecified-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"PrivilegeList\":\"SeTcbPrivilege\",\"SubjectUserName\":\"alice\",\"SubjectDomainName\":\"EXAMPLE\",\"IpAddress\":\"0.0.0.0\",\"IpPort\":\"49152\"},\"eventCode\":4672}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.goldenTicketDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "golden_ticket_detection" + ] + }, + { + "name": "bruteforce_attack ipv4", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":3221225578,\"IpAddress\":\"192.0.2.41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4625}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "192.0.2.41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "192.0.2.41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "bruteforce_attack ipv6", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":3221225578,\"IpAddress\":\"2001:db8::41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4625}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "2001:db8::41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "2001:db8::41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "bruteforce_attack dash-with-host", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":3221225578,\"IpAddress\":\"-\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4625}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "bruteforce_attack dash-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":3221225578,\"IpAddress\":\"-\",\"IpPort\":\"49152\"},\"eventCode\":4625}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "bruteforce_attack absent-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":3221225578,\"IpPort\":\"49152\"},\"eventCode\":4625}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "bruteforce_attack unspecified-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":3221225578,\"IpAddress\":\"0.0.0.0\",\"IpPort\":\"49152\"},\"eventCode\":4625}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "bruteforce_multiple_logon_failure_followed_by_success ipv4", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":0,\"IpAddress\":\"192.0.2.41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4624}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "192.0.2.41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "192.0.2.41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "bruteforce_multiple_logon_failure_followed_by_success" + ] + }, + { + "name": "bruteforce_multiple_logon_failure_followed_by_success ipv6", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":0,\"IpAddress\":\"2001:db8::41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4624}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "2001:db8::41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "2001:db8::41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "bruteforce_multiple_logon_failure_followed_by_success" + ] + }, + { + "name": "bruteforce_multiple_logon_failure_followed_by_success dash-with-host", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":0,\"IpAddress\":\"-\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4624}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_multiple_logon_failure_followed_by_success" + ] + }, + { + "name": "bruteforce_multiple_logon_failure_followed_by_success dash-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":0,\"IpAddress\":\"-\",\"IpPort\":\"49152\"},\"eventCode\":4624}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_multiple_logon_failure_followed_by_success" + ] + }, + { + "name": "bruteforce_multiple_logon_failure_followed_by_success absent-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":0,\"IpPort\":\"49152\"},\"eventCode\":4624}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_multiple_logon_failure_followed_by_success" + ] + }, + { + "name": "bruteforce_multiple_logon_failure_followed_by_success unspecified-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"Status\":0,\"IpAddress\":\"0.0.0.0\",\"IpPort\":\"49152\"},\"eventCode\":4624}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.bruteforceMultipleLogonFailureFollowedBySuccess": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "bruteforce_multiple_logon_failure_followed_by_success" + ] + }, + { + "name": "adfs_authentication_anomalies ipv4", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"AD FS\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"SubjectUserName\":\"alice\",\"IpAddress\":\"192.0.2.41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\",\"SubjectDomainName\":\"EXAMPLE\"},\"eventCode\":342,\"message\":\"token validation failed\"}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "AD FS", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "192.0.2.41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "192.0.2.41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.adfsAuthenticationAnomalies": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "adfs_authentication_anomalies" + ] + }, + { + "name": "adfs_authentication_anomalies ipv6", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"AD FS\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"SubjectUserName\":\"alice\",\"IpAddress\":\"2001:db8::41\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\",\"SubjectDomainName\":\"EXAMPLE\"},\"eventCode\":342,\"message\":\"token validation failed\"}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "AD FS", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "2001:db8::41", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "2001:db8::41", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.adfsAuthenticationAnomalies": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "adfs_authentication_anomalies" + ] + }, + { + "name": "adfs_authentication_anomalies dash-with-host", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"AD FS\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"SubjectUserName\":\"alice\",\"IpAddress\":\"-\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\",\"SubjectDomainName\":\"EXAMPLE\"},\"eventCode\":342,\"message\":\"token validation failed\"}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "AD FS", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.adfsAuthenticationAnomalies": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "adfs_authentication_anomalies" + ] + }, + { + "name": "adfs_authentication_anomalies dash-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"AD FS\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"SubjectUserName\":\"alice\",\"IpAddress\":\"-\",\"IpPort\":\"49152\",\"SubjectDomainName\":\"EXAMPLE\"},\"eventCode\":342,\"message\":\"token validation failed\"}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "AD FS", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "-", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.adfsAuthenticationAnomalies": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "adfs_authentication_anomalies" + ] + }, + { + "name": "adfs_authentication_anomalies absent-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"AD FS\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"SubjectUserName\":\"alice\",\"IpPort\":\"49152\",\"SubjectDomainName\":\"EXAMPLE\"},\"eventCode\":342,\"message\":\"token validation failed\"}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "AD FS", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.adfsAuthenticationAnomalies": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "adfs_authentication_anomalies" + ] + }, + { + "name": "adfs_authentication_anomalies unspecified-with-account", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"AD FS\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"SubjectUserName\":\"alice\",\"IpAddress\":\"0.0.0.0\",\"IpPort\":\"49152\",\"SubjectDomainName\":\"EXAMPLE\"},\"eventCode\":342,\"message\":\"token validation failed\"}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "AD FS", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.adfsAuthenticationAnomalies": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "adfs_authentication_anomalies" + ] + }, + { + "name": "No fabricated identity or port -", + "dataSource": "dc01", + "raw": "{\"timestamp\": \"2026-09-17T12:00:00Z\", \"provider_name\": \"Microsoft-Windows-Security-Auditing\", \"provider_guid\": \"{54849625-5478-4994-a5ba-3e3b0328c30d}\", \"recordId\": 1234, \"channel\": \"Security\", \"computer\": \"dc01.example.test\", \"data\": {\"IpAddress\": \"-\", \"IpPort\": \"-\", \"TargetUserName\": \"-\", \"WorkstationName\": \"-\"}, \"eventCode\": 4625}", + "expected": { + "log.data.IpAddress": "-" + }, + "absent": [ + "origin.ip", + "origin.port", + "origin.user", + "origin.host", + "target.user", + "log.authenticationSource", + "log.authenticationSourceType" + ], + "matches": [] + }, + { + "name": "No fabricated identity or port 65536", + "dataSource": "dc01", + "raw": "{\"timestamp\": \"2026-09-17T12:00:00Z\", \"provider_name\": \"Microsoft-Windows-Security-Auditing\", \"provider_guid\": \"{54849625-5478-4994-a5ba-3e3b0328c30d}\", \"recordId\": 1234, \"channel\": \"Security\", \"computer\": \"dc01.example.test\", \"data\": {\"IpAddress\": \"-\", \"IpPort\": \"65536\", \"TargetUserName\": \"-\", \"WorkstationName\": \"-\"}, \"eventCode\": 4625}", + "expected": { + "log.data.IpAddress": "-" + }, + "absent": [ + "origin.ip", + "origin.port", + "origin.user", + "origin.host", + "target.user", + "log.authenticationSource", + "log.authenticationSourceType" + ], + "matches": [] + }, + { + "name": "No fabricated identity or port 0", + "dataSource": "dc01", + "raw": "{\"timestamp\": \"2026-09-17T12:00:00Z\", \"provider_name\": \"Microsoft-Windows-Security-Auditing\", \"provider_guid\": \"{54849625-5478-4994-a5ba-3e3b0328c30d}\", \"recordId\": 1234, \"channel\": \"Security\", \"computer\": \"dc01.example.test\", \"data\": {\"IpAddress\": \"-\", \"IpPort\": \"0\", \"TargetUserName\": \"-\", \"WorkstationName\": \"-\"}, \"eventCode\": 4625}", + "expected": { + "log.data.IpAddress": "-" + }, + "absent": [ + "origin.ip", + "origin.port", + "origin.user", + "origin.host", + "target.user", + "log.authenticationSource", + "log.authenticationSourceType" + ], + "matches": [] + }, + { + "name": "No fabricated identity or port not-a-port", + "dataSource": "dc01", + "raw": "{\"timestamp\": \"2026-09-17T12:00:00Z\", \"provider_name\": \"Microsoft-Windows-Security-Auditing\", \"provider_guid\": \"{54849625-5478-4994-a5ba-3e3b0328c30d}\", \"recordId\": 1234, \"channel\": \"Security\", \"computer\": \"dc01.example.test\", \"data\": {\"IpAddress\": \"-\", \"IpPort\": \"not-a-port\", \"TargetUserName\": \"-\", \"WorkstationName\": \"-\"}, \"eventCode\": 4625}", + "expected": { + "log.data.IpAddress": "-" + }, + "absent": [ + "origin.ip", + "origin.port", + "origin.user", + "origin.host", + "target.user", + "log.authenticationSource", + "log.authenticationSourceType" + ], + "matches": [] + }, + { + "name": "NTLM workstation alias and successful numeric status", + "dataSource": "dc01", + "raw": "{\"eventCode\": 4776, \"computer\": \"dc01.example.test\", \"timestamp\": \"2026-09-17T12:00:00Z\", \"provider_name\": \"Microsoft-Windows-Security-Auditing\", \"channel\": \"Security\", \"data\": {\"TargetUserName\": \"alice\", \"Workstation\": \"CLIENT01\", \"Status\": 0}}", + "expected": { + "origin.host": "CLIENT01", + "origin.user": "alice", + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "actionResult": "success", + "log.authenticationSource": "CLIENT01", + "log.authenticationSourceType": "host", + "log.authenticationSourceDomain": "network-source" + }, + "absent": [ + "origin.ip", + "origin.port" + ], + "matches": [] + }, + { + "name": "Explicit credentials preserve actor and target roles", + "dataSource": "workstation01", + "raw": "{\"eventCode\": 4648, \"computer\": \"workstation01.example.test\", \"timestamp\": \"2026-09-17T12:00:00Z\", \"data\": {\"SubjectUserName\": \"actor\", \"SubjectDomainName\": \"CLIENT\", \"TargetUserName\": \"admin\", \"TargetDomainName\": \"DOMAIN\", \"ProcessName\": \"C:\\\\Windows\\\\System32\\\\runas.exe\", \"IpAddress\": \"-\", \"IpPort\": \"0\"}}", + "expected": { + "origin.user": "actor", + "origin.domain": "CLIENT", + "origin.path": "C:\\Windows\\System32\\runas.exe", + "target.user": "admin", + "target.domain": "DOMAIN", + "target.host": "workstation01.example.test", + "log.eventDataProcessName": "C:\\Windows\\System32\\runas.exe" + }, + "absent": [ + "origin.ip", + "origin.port" + ], + "matches": [] + }, + { + "name": "Authenticated account domain overrides service account subject domain", + "dataSource": "server01", + "raw": "{\"eventCode\": 4625, \"computer\": \"server01.example.test\", \"timestamp\": \"2026-09-17T12:00:00Z\", \"data\": {\"SubjectUserName\": \"SYSTEM\", \"SubjectDomainName\": \"NT AUTHORITY\", \"TargetUserName\": \"alice\", \"TargetDomainName\": \"EXAMPLE\", \"ProcessName\": \"C:\\\\Windows\\\\System32\\\\winlogon.exe\", \"IpAddress\": \"-\"}}", + "expected": { + "origin.user": "alice", + "origin.domain": "EXAMPLE", + "origin.path": "C:\\Windows\\System32\\winlogon.exe", + "log.eventDataSubjectDomainName": "NT AUTHORITY", + "log.eventDataSubjectUserName": "SYSTEM", + "log.authenticationCandidate.bruteforceAttack": "match" + }, + "absent": [ + "origin.ip" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "Unqualified account without domain is not a shared fallback", + "dataSource": "dc01", + "raw": "{\"eventCode\": 4625, \"data\": {\"IpAddress\": \"-\", \"TargetUserName\": \"alice\"}}", + "expected": { + "target.user": "alice", + "origin.user": "alice" + }, + "absent": [ + "origin.ip", + "log.authenticationSource", + "log.authenticationCandidate" + ], + "matches": [] + }, + { + "name": "Qualified UPN account fallback", + "dataSource": "dc01", + "raw": "{\"eventCode\": 4625, \"data\": {\"IpAddress\": \"-\", \"TargetUserName\": \"alice@example.test\"}}", + "expected": { + "origin.user": "alice@example.test", + "log.authenticationSource": "alice@example.test", + "log.authenticationSourceType": "user", + "log.authenticationSourceDomain": "qualified-upn" + }, + "absent": [ + "origin.ip" + ], + "matches": [ + "bruteforce_attack" + ] + }, + { + "name": "Forged candidate markers are not trusted", + "dataSource": "dc01", + "raw": "{\"eventCode\": 4625, \"authenticationCandidate\": {\"bruteforce_attack\": \"match\"}, \"authenticationSource\": \"alice\", \"authenticationSourceType\": \"user\", \"data\": {\"IpAddress\": \"-\", \"TargetUserName\": \"-\"}}", + "expected": {}, + "absent": [ + "log.authenticationCandidate", + "log.authenticationSource" + ], + "matches": [] + }, + { + "name": "NTLM numeric nonzero status fails", + "dataSource": "dc01", + "raw": "{\"eventCode\": 4776, \"computer\": \"dc01.example.test\", \"timestamp\": \"2026-09-17T12:00:00Z\", \"provider_name\": \"Microsoft-Windows-Security-Auditing\", \"channel\": \"Security\", \"data\": {\"TargetUserName\": \"alice\", \"Workstation\": \"CLIENT01\", \"Status\": 3221226036}}", + "expected": { + "origin.host": "CLIENT01", + "origin.user": "alice", + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "actionResult": "failure", + "log.authenticationSource": "CLIENT01", + "log.authenticationSourceType": "host", + "log.authenticationSourceDomain": "network-source" + }, + "absent": [ + "origin.ip", + "origin.port" + ], + "matches": [] + }, + { + "name": "Zero address expanded IPv6 uses account fallback", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"0:0:0:0:0:0:0:0\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0:0:0:0:0:0:0:0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "Zero address alternate compressed IPv6 uses account fallback", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"::0\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "::0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "Zero address mapped dotted IPv4 uses account fallback", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"::ffff:0.0.0.0\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "::ffff:0.0.0.0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "Zero address mapped hexadecimal IPv4 uses account fallback", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"::ffff:0:0\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "user", + "log.authenticationSource": "alice", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "::ffff:0:0", + "log.authenticationSourceDomain": "EXAMPLE", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "Zero address expanded IPv6 uses workstation fallback", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"0:0:0:0:0:0:0:0\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "host", + "log.authenticationSource": "client01", + "origin.port": 49152, + "origin.user": "alice", + "log.data.IpAddress": "0:0:0:0:0:0:0:0", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "origin.ip" + ], + "matches": [ + "kerberoasting_detection" + ] + }, + { + "name": "Zero address without qualified fallback is not pooled", + "dataSource": "dc01", + "raw": "{\"eventCode\":4625,\"data\":{\"IpAddress\":\"::ffff:0.0.0.0\",\"TargetUserName\":\"alice\"}}", + "expected": { + "target.user": "alice", + "origin.user": "alice", + "log.data.IpAddress": "::ffff:0.0.0.0" + }, + "absent": [ + "origin.ip", + "log.authenticationSource", + "log.authenticationCandidate" + ], + "matches": [] + }, + { + "name": "Valid mapped IPv4 remains an IP identity", + "dataSource": "dc01", + "raw": "{\"timestamp\":\"2026-09-17T12:00:00Z\",\"provider_name\":\"Microsoft-Windows-Security-Auditing\",\"provider_guid\":\"{54849625-5478-4994-a5ba-3e3b0328c30d}\",\"recordId\":1234,\"channel\":\"Security\",\"computer\":\"dc01.example.test\",\"data\":{\"TicketEncryptionType\":23,\"TicketOptions\":0,\"ServiceName\":\"sql-service\",\"TargetUserName\":\"alice\",\"TargetDomainName\":\"EXAMPLE\",\"IpAddress\":\"::ffff:192.0.2.10\",\"WorkstationName\":\"client01\",\"IpPort\":\"49152\"},\"eventCode\":4769}", + "expected": { + "target.host": "dc01.example.test", + "deviceTime": "2026-09-17T12:00:00Z", + "log.providerName": "Microsoft-Windows-Security-Auditing", + "log.authenticationSourceType": "ip", + "log.authenticationSource": "::ffff:192.0.2.10", + "origin.port": 49152, + "origin.user": "alice", + "origin.ip": "::ffff:192.0.2.10", + "log.authenticationSourceDomain": "network-source", + "log.authenticationCandidate.kerberoastingDetection": "match" + }, + "absent": [ + "log.unparsedOriginIp", + "log.data.IpAddress" + ], + "matches": [ + "kerberoasting_detection" + ] + } +] diff --git a/plugins/alerts/windows_contract_test.go b/plugins/alerts/windows_contract_test.go new file mode 100644 index 000000000..1c2a62585 --- /dev/null +++ b/plugins/alerts/windows_contract_test.go @@ -0,0 +1,621 @@ +package main + +// Offline Windows parser and correlation contracts. JSON key sanitization, +// rename/cast/grok/add/trim/delete are modeled from the documented pipeline; +// the closed EventProcessor is not executed. CEL, final Event conversion, +// placeholder resolution, query construction and threshold decisions use go-sdk. +// History searches reach only a local mock, never a customer instance. +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" + "testing" + "time" + + sdkos "github.com/threatwinds/go-sdk/os" + "google.golang.org/protobuf/types/known/structpb" + "text/template" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/threatwinds/go-sdk/utils" + "github.com/tidwall/gjson" + "google.golang.org/protobuf/encoding/protojson" +) + +type winFixture struct { + Name string `json:"name"` + DataSource string `json:"dataSource"` + Raw string `json:"raw"` + Expected map[string]any `json:"expected"` + Absent []string `json:"absent"` + Matches []string `json:"matches"` +} + +func winPut(m map[string]any, path string, value any, remove bool) { + p := strings.Split(path, ".") + for _, k := range p[:len(p)-1] { + n, ok := m[k].(map[string]any) + if !ok { + if remove { + return + } + n = map[string]any{} + m[k] = n + } + m = n + } + if remove { + delete(m, p[len(p)-1]) + } else { + m[p[len(p)-1]] = value + } +} +func winGet(m map[string]any, p string) (any, bool) { + var v any = m + for _, k := range strings.Split(p, ".") { + n, ok := v.(map[string]any) + if !ok { + return nil, false + } + v, ok = n[k] + if !ok { + return nil, false + } + } + return v, true +} +func winConfig(t *testing.T) *plugins.Config { + t.Helper() + b, e := utils.ReadPbYaml("../../filters/windows/windows-events.yml") + if e != nil { + t.Fatal(e) + } + c := new(plugins.Config) + if e = protojson.Unmarshal(b, c); e != nil { + t.Fatal(e) + } + return c +} +func winRegex(t *testing.T, g *plugins.Grok, cfg *plugins.Config) *regexp.Regexp { + t.Helper() + var pattern strings.Builder + for i, p := range g.Patterns { + if p.FieldName != "" { + fmt.Fprintf(&pattern, "(?P%s)", i, p.Pattern) + } else { + pattern.WriteString("(?:" + p.Pattern + ")") + } + } + pats := map[string]string{"greedy": ".*"} + for k, v := range cfg.Patterns { + pats[k] = v + } + tmpl, e := template.New("grok").Option("missingkey=error").Parse(pattern.String()) + if e != nil { + t.Fatal(e) + } + var b bytes.Buffer + if e = tmpl.Execute(&b, pats); e != nil { + t.Fatal(e) + } + r, e := regexp.Compile(b.String()) + if e != nil { + t.Fatal(e) + } + return r +} +func winParse(t *testing.T, cfg *plugins.Config, raw string, dataSource string, cache *plugins.CELCache) string { + t.Helper() + draft := map[string]any{"raw": raw, "dataType": "wineventlog", "dataSource": dataSource, "log": map[string]any{}} + for _, stage := range cfg.Pipeline { + for _, s := range stage.Steps { + b, e := protojson.Marshal(s) + if e != nil { + t.Fatal(e) + } + var step map[string]map[string]any + if e = json.Unmarshal(b, &step); e != nil { + t.Fatal(e) + } + for kind, body := range step { + if w, ok := body["where"].(string); ok && w != "" { + snapshot, err := json.Marshal(draft) + if err != nil { + t.Fatal(err) + } + match, e := cache.Eval(w, string(snapshot)) + if e != nil { + t.Fatal(e) + } + if !match { + continue + } + } + switch kind { + case "grok": + g := s.Grok + src := g.Source + if src == "" { + src = "raw" + } + v, ok := winGet(draft, src) + if !ok { + continue + } + str, ok := v.(string) + if !ok { + t.Fatalf("non-string grok source %s", src) + } + r := winRegex(t, g, cfg) + m := r.FindStringSubmatch(str) + if m == nil { + continue + } + for i, p := range g.Patterns { + if p.FieldName != "" { + winPut(draft, p.FieldName, m[r.SubexpIndex(fmt.Sprintf("f%d", i))], false) + } + } + case "rename": + for _, p := range s.Rename.From { + if v, ok := winGet(draft, p); ok { + winPut(draft, s.Rename.To, v, false) + winPut(draft, p, nil, true) + break + } + } + case "trim": + for _, p := range s.Trim.Fields { + if v, ok := winGet(draft, p); ok { + str, ok := v.(string) + if !ok { + continue + } + switch s.Trim.Function { + case "prefix": + str = strings.TrimPrefix(str, s.Trim.Substring) + case "suffix": + str = strings.TrimSuffix(str, s.Trim.Substring) + default: + t.Fatalf("unsupported trim %s", s.Trim.Function) + } + winPut(draft, p, str, false) + } + } + case "add": + winPut(draft, s.Add.Params["key"].GetStringValue(), s.Add.Params["value"].AsInterface(), false) + case "delete": + for _, p := range s.Delete.Fields { + winPut(draft, p, nil, true) + } + case "json": + source, ok := winGet(draft, s.Json.Source) + if !ok { + continue + } + str, ok := source.(string) + if !ok { + t.Fatalf("JSON source is not a string") + } + var parsed map[string]any + if e := json.Unmarshal([]byte(str), &parsed); e != nil { + t.Fatal(e) + } + for key, value := range winSanitizeJSON(parsed) { + winPut(draft, "log."+key, value, false) + } + case "cast": + for _, field := range s.Cast.Fields { + if value, ok := winGet(draft, field); ok { + switch s.Cast.To { + case "string": + winPut(draft, field, utils.CastString(value), false) + case "int": + winPut(draft, field, utils.CastInt64(value), false) + default: + t.Fatalf("unsupported cast %s", s.Cast.To) + } + } + } + case "drop": + t.Fatal("fixture unexpectedly dropped") + default: + t.Fatalf("unsupported filter step %s", kind) + } + } + } + } + b, e := json.Marshal(draft) + if e != nil { + t.Fatal(e) + } + in := string(b) + ev := new(plugins.Event) + if e = utils.StringToProtoMessage(&in, ev); e != nil { + t.Fatal(e) + } + out, e := utils.ProtoMessageToString(ev) + if e != nil { + t.Fatal(e) + } + return *out +} +func winRules(t *testing.T) map[string]*plugins.Rule { + t.Helper() + paths, e := filepath.Glob("../../rules/windows/*.yml") + if e != nil { + t.Fatal(e) + } + out := map[string]*plugins.Rule{} + for _, p := range paths { + b, e := utils.ReadPbYaml(p) + if e != nil { + t.Fatal(e) + } + r := new(plugins.Rule) + if e = protojson.Unmarshal(b, r); e != nil { + t.Fatal(e) + } + r.Normalize() + out[strings.TrimSuffix(filepath.Base(p), ".yml")] = r + } + return out +} + +func winSanitizeJSON(input map[string]any) map[string]any { + out := map[string]any{} + for key, value := range input { + utils.SanitizeField(&key) + if nested, ok := value.(map[string]any); ok { + value = winSanitizeJSON(nested) + } + out[key] = value + } + return out +} + +func winFixtures(t *testing.T) []winFixture { + t.Helper() + b, e := os.ReadFile("testdata/windows_raw.json") + if e != nil { + t.Fatal(e) + } + var fixtures []winFixture + if e = json.Unmarshal(b, &fixtures); e != nil { + t.Fatal(e) + } + return fixtures +} + +func TestWindowsRawContracts(t *testing.T) { + cfg, rules, cache := winConfig(t), winRules(t), plugins.NewCELCache("windows-raw") + for _, f := range winFixtures(t) { + t.Run(f.Name, func(t *testing.T) { + out := winParse(t, cfg, f.Raw, f.DataSource, cache) + for field, want := range f.Expected { + got := gjson.Get(out, field) + if !got.Exists() || !reflect.DeepEqual(got.Value(), want) { + t.Errorf("%s got %v want %v", field, got.Value(), want) + } + } + for _, field := range f.Absent { + if gjson.Get(out, field).Exists() { + t.Errorf("unexpected %s", field) + } + } + if gjson.Get(out, "raw").String() != f.Raw { + t.Error("raw changed") + } + for name, r := range rules { + if len(r.Correlation) == 0 || !strings.Contains(r.Where, "log.authenticationSourceDomain") { + continue + } + matched, e := cache.Eval(r.Where, out) + if e != nil { + t.Fatal(e) + } + marker := gjson.Get(out, winCandidateField(name)).String() == "match" + if marker != matched { + t.Errorf("%s marker=%v predicate=%v", name, marker, matched) + } + } + if len(f.Matches) == 0 { + for name, r := range rules { + matched, e := cache.Eval(r.Where, out) + if e != nil { + t.Fatalf("%s compile/eval: %v", name, e) + } + if matched { + t.Errorf("identity-less negative fixture matched %s", name) + } + } + } + for _, name := range f.Matches { + r := rules[name] + if r == nil { + t.Fatalf("unknown rule %s", name) + } + got, e := cache.Eval(r.Where, out) + if e != nil || !got { + t.Fatalf("%s where=%v error=%v", name, got, e) + } + for _, search := range r.Correlation { + for _, term := range search.With { + if value := term.Value.GetStringValue(); strings.HasPrefix(value, "{{.") { + field := strings.TrimSuffix(strings.TrimPrefix(value, "{{."), "}}") + if !gjson.Get(out, field).Exists() { + t.Errorf("%s unresolved %s", name, field) + } + } + } + } + } + }) + } +} + +// Evaluate the generated OpenSearch query against a tiny local history. This is +// deliberately limited to exact terms and the timestamp range these rules use. +func winHistoryMatches(t *testing.T, query string, event string) bool { + t.Helper() + clauses := append(gjson.Get(query, "query.bool.filter").Array(), gjson.Get(query, "query.bool.must").Array()...) + if len(clauses) < 5 { + t.Errorf("missing history constraints: %s", query) + } + for _, term := range clauses { + if object := term.Get("term"); object.Exists() { + for field, value := range object.Map() { + got := gjson.Get(event, strings.TrimSuffix(field, ".keyword")) + if !got.Exists() || got.String() != value.Get("value").String() { + return false + } + } + } else if object := term.Get("range"); object.Exists() { + for field, conditions := range object.Map() { + got, e := time.Parse(time.RFC3339Nano, gjson.Get(event, field).String()) + if e != nil { + t.Fatal(e) + } + for op, value := range conditions.Map() { + bound, e := time.Parse(time.RFC3339Nano, value.String()) + if e != nil { + t.Fatal(e) + } + if op != "gte" { + t.Fatalf("unsupported range %s", op) + } + if got.Before(bound) { + return false + } + } + } + } else { + t.Fatalf("unsupported history clause %s", term.Raw) + } + } + return true +} + +func TestWindowsSDKHistory(t *testing.T) { + cfg, rules, cache := winConfig(t), winRules(t), plugins.NewCELCache("windows-history") + var history []string + var requests int + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if strings.HasSuffix(r.URL.Path, "/_mapping") { + _, _ = w.Write([]byte(`{"v11-log-wineventlog-test":{"mappings":{"properties":{"@timestamp":{"type":"date"},"dataSource":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"log":{"properties":{"authenticationSource":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"authenticationSourceType":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"eventCode":{"type":"long"},"eventDataTicketEncryptionType":{"type":"long"},"eventDataPreAuthType":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"authenticationSourceDomain":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"authenticationCandidate":{"properties":{"kerberoastingDetection":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"asrepRoastingDetection":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"silverTicketDetection":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"goldenTicketDetection":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"adfsAuthenticationAnomalies":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"bruteforceAttack":{"type":"text","fields":{"keyword":{"type":"keyword"}}},"bruteforceMultipleLogonFailureFollowedBySuccess":{"type":"text","fields":{"keyword":{"type":"keyword"}}}}}}},"target":{"properties":{"user":{"type":"text","fields":{"keyword":{"type":"keyword"}}}}}}}}}`)) + return + } + requests++ + if r.URL.Path != "/v11-log-wineventlog-*/_search" { + t.Errorf("unexpected path %s", r.URL.Path) + } + body, e := io.ReadAll(r.Body) + if e != nil { + t.Error(e) + } + hits := []map[string]any{} + for _, doc := range history { + if winHistoryMatches(t, string(body), doc) { + var source map[string]any + if e := json.Unmarshal([]byte(doc), &source); e != nil { + t.Error(e) + } + hits = append(hits, map[string]any{"_id": fmt.Sprint(len(hits)), "_index": "v11-log-wineventlog-test", "_source": source}) + } + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{"took": 1, "timed_out": false, "_shards": map[string]any{"total": 1, "successful": 1, "failed": 0}, "hits": map[string]any{"total": map[string]any{"value": len(hits), "relation": "eq"}, "hits": hits}}) + })) + defer server.Close() + if e := sdkos.Connect([]string{server.URL}, "", ""); e != nil { + t.Fatal(e) + } + mutate := func(event, field string, value any) string { + var doc map[string]any + if e := json.Unmarshal([]byte(event), &doc); e != nil { + t.Fatal(e) + } + winPut(doc, field, value, value == nil) + b, e := json.Marshal(doc) + if e != nil { + t.Fatal(e) + } + return string(b) + } + for _, f := range winFixtures(t) { + if len(f.Matches) == 0 { + continue + } + t.Run(f.Name, func(t *testing.T) { + trigger := winParse(t, cfg, f.Raw, f.DataSource, cache) + for _, name := range f.Matches { + r := rules[name] + for _, search := range r.Correlation { + previous := mutate(trigger, "@timestamp", time.Now().Add(-time.Minute).UTC().Format(time.RFC3339Nano)) + // Success-after-failures searches 4625, not the triggering 4624. + for _, term := range search.With { + if term.Field == "log.eventCode" && term.Value.GetStringValue() == "" { + var failedRaw map[string]any + if e := json.Unmarshal([]byte(f.Raw), &failedRaw); e != nil { + t.Fatal(e) + } + failedRaw["eventCode"] = term.Value.AsInterface() + b, e := json.Marshal(failedRaw) + if e != nil { + t.Fatal(e) + } + previous = winParse(t, cfg, string(b), f.DataSource, cache) + previous = mutate(previous, "@timestamp", time.Now().Add(-time.Minute).UTC().Format(time.RFC3339Nano)) + } + } + history = nil + for i := uint64(0); i < search.Count-1; i++ { + history = append(history, previous) + } + ok, _, e := search.Execute(&trigger) + if e != nil || ok { + t.Fatalf("%s below threshold result=%v error=%v", name, ok, e) + } + history = append(history, previous) + ok, _, e = search.Execute(&trigger) + if e != nil || !ok { + t.Fatalf("%s threshold result=%v error=%v", name, ok, e) + } + for _, field := range []string{"dataSource", "log.authenticationSource", "log.authenticationSourceType", "log.authenticationSourceDomain", "log.authenticationCandidate", "log.eventCode"} { + history = nil + for i := uint64(0); i < search.Count; i++ { + history = append(history, mutate(previous, field, "different")) + } + ok, _, e = search.Execute(&trigger) + if e != nil || ok { + t.Fatalf("%s mixed %s result=%v error=%v", name, field, ok, e) + } + } + history = nil + for i := uint64(0); i < search.Count; i++ { + history = append(history, mutate(previous, "@timestamp", time.Now().Add(-time.Hour).UTC().Format(time.RFC3339Nano))) + } + ok, _, e = search.Execute(&trigger) + if e != nil || ok { + t.Fatalf("%s expired history result=%v error=%v", name, ok, e) + } + } + for _, field := range []string{"log.authenticationSource", "log.authenticationSourceType", "log.authenticationSourceDomain"} { + missing := mutate(trigger, field, nil) + ok, e := cache.Eval(r.Where, missing) + if e != nil || ok { + t.Errorf("%s accepted missing %s", name, field) + } + } + } + }) + } + // Reproduce the original regression with the actual SDK. An `or` sibling + // cannot rescue this: Execute returns before it reaches Or on missing IP. + before := requests + legacy := &plugins.SearchRequest{IndexPattern: "v11-log-wineventlog-*", Count: 3, With: []*plugins.Expression{{Field: "origin.ip.keyword", Operator: "filter_term", Value: structpb.NewStringValue("{{.origin.ip}}")}}} + legacy.Or = []*plugins.SearchRequest{{IndexPattern: "v11-log-wineventlog-*", Count: 1, With: []*plugins.Expression{{Field: "target.user.keyword", Operator: "filter_term", Value: structpb.NewStringValue("{{.target.user}}")}}}} + ipless := `{"target":{"user":"analyst"}}` + ok, _, e := legacy.Execute(&ipless) + if e == nil || ok || requests != before { + t.Fatalf("nil placeholder unexpectedly queried: %v %v", ok, e) + } +} + +// Opt-in replay of bounded private OpenSearch evidence. No customer log content, +// identities or requests are printed; this tests projected normalization and CEL, +// not the production parser or a historical customer query. +func TestWindowsPrivateEvidence(t *testing.T) { + dir := os.Getenv("UTM_WINDOWS_EVIDENCE") + if dir == "" { + t.Skip("set UTM_WINDOWS_EVIDENCE to the private bounded evidence directory") + } + paths, e := filepath.Glob(filepath.Join(dir, "windows-*.json")) + if e != nil { + t.Fatal(e) + } + if len(paths) == 0 { + t.Fatal("no evidence files") + } + cfg, rules, cache := winConfig(t), winRules(t), plugins.NewCELCache("windows-private-evidence") + samples, ipless := 0, 0 + matches := map[string]int{} + observed := map[string]int{} + for _, path := range paths { + b, e := os.ReadFile(path) + if e != nil { + t.Fatal(e) + } + seen := map[string]bool{} + for _, bucket := range gjson.GetBytes(b, "aggregations.codes.buckets").Array() { + hits := append(bucket.Get("representative.hits.hits").Array(), bucket.Get("missing_source.representative.hits.hits").Array()...) + for _, hit := range hits { + id := hit.Get("_id").String() + if seen[id] { + continue + } + seen[id] = true + src := hit.Get("_source") + raw := src.Get("raw").String() + out := winParse(t, cfg, raw, src.Get("dataSource").String(), cache) + samples++ + if !gjson.Get(out, "origin.ip").Exists() { + ipless++ + } + for rawField, standard := range map[string]string{"computer": "target.host", "timestamp": "deviceTime", "data.Workstation": "origin.host", "data.ProcessName": "origin.path"} { + source := gjson.Get(raw, rawField) + if source.Type != gjson.String || source.String() == "" || source.String() == "-" { + continue + } + if gjson.Get(out, standard).String() != source.String() { + t.Errorf("%s projected %s differs from source %s", id, standard, rawField) + } + if src.Get(standard).String() != source.String() { + observed[standard]++ + } + } + for name, r := range rules { + yes, e := cache.Eval(r.Where, out) + if e != nil { + t.Errorf("%s %s CEL error: %v", id, name, e) + } + if !yes { + continue + } + matches[name]++ + for _, search := range r.Correlation { + for _, term := range search.With { + value := term.Value.GetStringValue() + if !strings.HasPrefix(value, "{{.") { + continue + } + field := strings.TrimSuffix(strings.TrimPrefix(value, "{{."), "}}") + if !gjson.Get(out, field).Exists() { + t.Errorf("%s %s unresolved placeholder %s", id, name, field) + } + } + } + } + } + } + } + t.Logf("private unique raw samples=%d projected IP-less=%d standard field mismatches in stored output=%v projected CEL candidates=%v", samples, ipless, observed, matches) +} + +func winCandidateField(ruleName string) string { + parts := strings.Split(ruleName, "_") + key := parts[0] + for _, p := range parts[1:] { + key += strings.ToUpper(p[:1]) + p[1:] + } + return "log.authenticationCandidate." + key +} diff --git a/rules/windows/adfs_authentication_anomalies.yml b/rules/windows/adfs_authentication_anomalies.yml index e0c052b9d..c57f002c2 100644 --- a/rules/windows/adfs_authentication_anomalies.yml +++ b/rules/windows/adfs_authentication_anomalies.yml @@ -24,15 +24,38 @@ description: | 5. Consider implementing IP-based blocking if malicious activity is confirmed 6. Review ADFS configuration for security hardening opportunities 7. Correlate with other authentication events across the domain -where: equals("log.providerName", "AD FS") && (equals("log.eventCode", "342") || equals("log.eventCode", "516")) && contains("log.message", "token validation failed") +where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( + equals("log.providerName", "AD FS") && (equals("log.eventCode", "342") || equals("log.eventCode", "516")) && contains("log.message", "token validation failed") + ) afterEvents: - indexPattern: v11-log-wineventlog-* with: - - field: origin.ip.keyword + - field: dataSource.keyword operator: filter_term - value: '{{.origin.ip}}' + value: '{{.dataSource}}' + - field: log.authenticationSource.keyword + operator: filter_term + value: '{{.log.authenticationSource}}' + - field: log.authenticationSourceType.keyword + operator: filter_term + value: '{{.log.authenticationSourceType}}' + - field: log.authenticationSourceDomain.keyword + operator: filter_term + value: '{{.log.authenticationSourceDomain}}' + - field: log.authenticationCandidate.adfsAuthenticationAnomalies.keyword + operator: filter_term + value: match + - field: log.eventCode + operator: filter_term + value: '{{.log.eventCode}}' within: 10m count: 10 groupBy: - - origin.ip + - dataSource + - lastEvent.log.authenticationSourceType + - lastEvent.log.authenticationSource + - lastEvent.log.authenticationSourceDomain - target.user diff --git a/rules/windows/asrep_roasting_detection.yml b/rules/windows/asrep_roasting_detection.yml index 09eecd197..f998b3487 100644 --- a/rules/windows/asrep_roasting_detection.yml +++ b/rules/windows/asrep_roasting_detection.yml @@ -28,20 +28,48 @@ description: | 5. Audit Active Directory for accounts with DONT_REQUIRE_PREAUTH flag 6. Monitor for subsequent credential usage from the requesting IP where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( equals("log.eventCode", "4768") && equals("log.channel", "Security") && equals("log.eventDataTicketEncryptionType", "23") && equals("log.eventDataPreAuthType", "0") && !regexMatch("target.user", "(?i)\\$$") && exists("target.user") + ) afterEvents: - indexPattern: v11-log-wineventlog-* with: - - field: origin.ip.keyword + - field: dataSource.keyword operator: filter_term - value: '{{.origin.ip}}' + value: '{{.dataSource}}' + - field: log.authenticationSource.keyword + operator: filter_term + value: '{{.log.authenticationSource}}' + - field: log.authenticationSourceType.keyword + operator: filter_term + value: '{{.log.authenticationSourceType}}' + - field: log.authenticationSourceDomain.keyword + operator: filter_term + value: '{{.log.authenticationSourceDomain}}' + - field: log.authenticationCandidate.asrepRoastingDetection.keyword + operator: filter_term + value: match + - field: log.eventCode + operator: filter_term + value: '{{.log.eventCode}}' + - field: log.eventDataTicketEncryptionType + operator: filter_term + value: '{{.log.eventDataTicketEncryptionType}}' + - field: log.eventDataPreAuthType + operator: filter_term + value: '{{.log.eventDataPreAuthType}}' within: 15m count: 3 groupBy: - - origin.ip - - origin.host + - dataSource + - lastEvent.log.authenticationSourceType + - lastEvent.log.authenticationSource + - lastEvent.log.authenticationSourceDomain + - target.user diff --git a/rules/windows/audit_log_was_cleared.yml b/rules/windows/audit_log_was_cleared.yml index b9d2c7a5b..2e6c084e9 100644 --- a/rules/windows/audit_log_was_cleared.yml +++ b/rules/windows/audit_log_was_cleared.yml @@ -14,5 +14,5 @@ references: - "https://attack.mitre.org/techniques/T1070/001/" where: equals("log.eventCode", 1102) groupBy: - - origin.ip + - adversary.ip - target.user diff --git a/rules/windows/bruteforce_attack.yml b/rules/windows/bruteforce_attack.yml index eb664c56c..7e82bd3d9 100644 --- a/rules/windows/bruteforce_attack.yml +++ b/rules/windows/bruteforce_attack.yml @@ -13,25 +13,41 @@ description: "This rule is triggered when a pattern of repeated and rapid login references: - "https://attack.mitre.org/tactics/TA0006/" - "https://attack.mitre.org/techniques/T1110/" -where: equals("log.eventCode", 4625) +where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && exists("target.user") && + ( + equals("log.eventCode", 4625) + ) afterEvents: - - indexPattern: v11-log-wineventlog-* - with: - - field: log.eventCode - operator: filter_term - value: "{{.log.eventCode}}" - - field: target.user.keyword - operator: filter_term - value: "{{.target.user}}" - - field: origin.host.keyword - operator: filter_term - value: "{{.origin.host}}" - - field: origin.ip.keyword - operator: filter_term - value: "{{.origin.ip}}" - within: 5m - count: 10 + - indexPattern: v11-log-wineventlog-* + with: + - field: dataSource.keyword + operator: filter_term + value: '{{.dataSource}}' + - field: log.authenticationSource.keyword + operator: filter_term + value: '{{.log.authenticationSource}}' + - field: log.authenticationSourceType.keyword + operator: filter_term + value: '{{.log.authenticationSourceType}}' + - field: log.authenticationSourceDomain.keyword + operator: filter_term + value: '{{.log.authenticationSourceDomain}}' + - field: log.authenticationCandidate.bruteforceAttack.keyword + operator: filter_term + value: match + - field: log.eventCode + operator: filter_term + value: '{{.log.eventCode}}' + - field: target.user.keyword + operator: filter_term + value: '{{.target.user}}' + within: 5m + count: 10 deduplicateBy: - - origin.host + - dataSource + - lastEvent.log.authenticationSourceType + - lastEvent.log.authenticationSource + - lastEvent.log.authenticationSourceDomain - target.user - - origin.ip diff --git a/rules/windows/bruteforce_multiple_logon_failure_followed_by_success.yml b/rules/windows/bruteforce_multiple_logon_failure_followed_by_success.yml index 5bccb7233..e5a2ea5a4 100644 --- a/rules/windows/bruteforce_multiple_logon_failure_followed_by_success.yml +++ b/rules/windows/bruteforce_multiple_logon_failure_followed_by_success.yml @@ -13,25 +13,41 @@ description: "This rule is triggered when a sequence of multiple failed login at references: - "https://attack.mitre.org/tactics/TA0006/" - "https://attack.mitre.org/techniques/T1110/" -where: equals("log.eventCode", 4624) +where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && exists("target.user") && + ( + equals("log.eventCode", 4624) + ) afterEvents: - - indexPattern: v11-log-wineventlog-* - with: - - field: log.eventCode - operator: filter_term - value: 4625 - - field: target.user.keyword - operator: filter_term - value: "{{.target.user}}" - - field: origin.host.keyword - operator: filter_term - value: "{{.origin.host}}" - - field: origin.ip.keyword - operator: filter_term - value: "{{.origin.ip}}" - within: 5m - count: 10 + - indexPattern: v11-log-wineventlog-* + with: + - field: dataSource.keyword + operator: filter_term + value: '{{.dataSource}}' + - field: log.authenticationSource.keyword + operator: filter_term + value: '{{.log.authenticationSource}}' + - field: log.authenticationSourceType.keyword + operator: filter_term + value: '{{.log.authenticationSourceType}}' + - field: log.authenticationSourceDomain.keyword + operator: filter_term + value: '{{.log.authenticationSourceDomain}}' + - field: log.authenticationCandidate.bruteforceAttack.keyword + operator: filter_term + value: match + - field: log.eventCode + operator: filter_term + value: 4625 + - field: target.user.keyword + operator: filter_term + value: '{{.target.user}}' + within: 5m + count: 10 deduplicateBy: - - origin.ip + - dataSource + - lastEvent.log.authenticationSourceType + - lastEvent.log.authenticationSource + - lastEvent.log.authenticationSourceDomain - target.user - - origin.host diff --git a/rules/windows/certificate_services_abuse.yml b/rules/windows/certificate_services_abuse.yml index b5e1f4ae3..317a15782 100644 --- a/rules/windows/certificate_services_abuse.yml +++ b/rules/windows/certificate_services_abuse.yml @@ -27,4 +27,4 @@ description: | where: (equals("log.eventCode", "4886") || equals("log.eventCode", "4887")) && equals("log.providerName", "Microsoft-Windows-Security-Auditing") && (contains("log.eventDataSubjectUserName", "$") || equals("log.eventDataSubjectUserName", "ANONYMOUS LOGON")) groupBy: - lastEvent.log.eventDataSubjectUserName - - origin.host + - adversary.host diff --git a/rules/windows/golden_ticket_detection.yml b/rules/windows/golden_ticket_detection.yml index 9df408ff3..cfe85948b 100644 --- a/rules/windows/golden_ticket_detection.yml +++ b/rules/windows/golden_ticket_detection.yml @@ -30,12 +30,15 @@ description: | 7. Consider rebuilding the domain if compromise is confirmed 8. Implement Kerberos armoring and constrained delegation where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( ( equals("log.eventCode", "4769") && equals("log.channel", "Security") && equals("log.eventDataServiceName", "krbtgt") && !equals("log.eventDataStatus", "0") && - exists("origin.ip") + exists("log.authenticationSource") ) || ( equals("log.eventCode", "4768") && @@ -51,14 +54,33 @@ where: | !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE)$") && !regexMatch("log.eventDataSubjectUserName", "(?i)\\$$") ) + ) afterEvents: - indexPattern: v11-log-wineventlog-* with: - - field: origin.host + - field: dataSource.keyword + operator: filter_term + value: '{{.dataSource}}' + - field: log.authenticationSource.keyword + operator: filter_term + value: '{{.log.authenticationSource}}' + - field: log.authenticationSourceType.keyword + operator: filter_term + value: '{{.log.authenticationSourceType}}' + - field: log.authenticationSourceDomain.keyword + operator: filter_term + value: '{{.log.authenticationSourceDomain}}' + - field: log.authenticationCandidate.goldenTicketDetection.keyword + operator: filter_term + value: match + - field: log.eventCode operator: filter_term - value: '{{.origin.host}}' + value: '{{.log.eventCode}}' within: 30m count: 3 groupBy: - - origin.host + - dataSource + - lastEvent.log.authenticationSourceType + - lastEvent.log.authenticationSource + - lastEvent.log.authenticationSourceDomain - target.user diff --git a/rules/windows/kerberoasting_detection.yml b/rules/windows/kerberoasting_detection.yml index eb2a0c2ab..98ae74b87 100644 --- a/rules/windows/kerberoasting_detection.yml +++ b/rules/windows/kerberoasting_detection.yml @@ -31,6 +31,9 @@ description: | 7. Enable Group Managed Service Accounts (gMSA) where possible 8. Monitor for follow-up lateral movement using obtained credentials where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( equals("log.eventCode", "4769") && equals("log.channel", "Security") && equals("log.eventDataTicketEncryptionType", "23") && @@ -38,14 +41,36 @@ where: | !equals("log.eventDataServiceName", "krbtgt") && !oneOf("log.eventDataTicketOptions", ["1082195968", "1082130432", "1082130432"]) && exists("log.eventDataServiceName") + ) afterEvents: - indexPattern: v11-log-wineventlog-* with: - - field: origin.ip.keyword + - field: dataSource.keyword operator: filter_term - value: '{{.origin.ip}}' + value: '{{.dataSource}}' + - field: log.authenticationSource.keyword + operator: filter_term + value: '{{.log.authenticationSource}}' + - field: log.authenticationSourceType.keyword + operator: filter_term + value: '{{.log.authenticationSourceType}}' + - field: log.authenticationSourceDomain.keyword + operator: filter_term + value: '{{.log.authenticationSourceDomain}}' + - field: log.authenticationCandidate.kerberoastingDetection.keyword + operator: filter_term + value: match + - field: log.eventCode + operator: filter_term + value: '{{.log.eventCode}}' + - field: log.eventDataTicketEncryptionType + operator: filter_term + value: '{{.log.eventDataTicketEncryptionType}}' within: 15m count: 3 groupBy: - - origin.ip + - dataSource + - lastEvent.log.authenticationSourceType + - lastEvent.log.authenticationSource + - lastEvent.log.authenticationSourceDomain - target.user diff --git a/rules/windows/lsass_memdump_handle_access.yml b/rules/windows/lsass_memdump_handle_access.yml index e0fca6615..e01d84ef6 100644 --- a/rules/windows/lsass_memdump_handle_access.yml +++ b/rules/windows/lsass_memdump_handle_access.yml @@ -21,5 +21,5 @@ references: where: equals("log.eventCode", 4656) && regexMatch("log.eventDataObjectName", "(:\\Windows\\System32\\lsass.exe|\\Device\\HarddiskVolume[A-Za-z?:\\]([A-Za-z?])?\\Windows\\System32\\lsass.exe)") && !regexMatch("log.eventDataProcessName", "(:\\Program Files\\(.+).exe|:\\Program Files (x86)\\(.+).exe|:\\Windows\\system32\\wbem\\WmiPrvSE.exe|:\\Windows\\System32\\dllhost.exe|:\\Windows\\System32\\svchost.exe|:\\Windows\\System32\\msiexec.exe|:\\ProgramData\\Microsoft\\Windows Defender\\(.+).exe|:\\Windows\\explorer.exe)") && oneOf("log.eventDataAccessMask", ["2097151", "4112", "1040", "1180185", "2031615"]) groupBy: - - origin.ip + - adversary.ip - target.user diff --git a/rules/windows/possible_exploit_over_reverse_tunneling_using_stolen_credentials.yml b/rules/windows/possible_exploit_over_reverse_tunneling_using_stolen_credentials.yml index 4be3d57c1..b28ce6ac1 100644 --- a/rules/windows/possible_exploit_over_reverse_tunneling_using_stolen_credentials.yml +++ b/rules/windows/possible_exploit_over_reverse_tunneling_using_stolen_credentials.yml @@ -14,5 +14,5 @@ references: - "https://attack.mitre.org/techniques/T1021/001/" where: equals("log.eventDataLogonType", "10") && oneOf("origin.ip", ["::1", "127.0.0.1"]) && oneOf("log.eventCode", [528, 540, 673, 4624, 4769]) groupBy: - - origin.ip + - adversary.ip - target.user diff --git a/rules/windows/ransom_multiple_file_deletion.yml b/rules/windows/ransom_multiple_file_deletion.yml index 2073ade06..96131cb11 100644 --- a/rules/windows/ransom_multiple_file_deletion.yml +++ b/rules/windows/ransom_multiple_file_deletion.yml @@ -36,5 +36,5 @@ afterEvents: within: 5m count: 50 groupBy: - - origin.ip + - adversary.ip - target.user diff --git a/rules/windows/sam_database_access.yml b/rules/windows/sam_database_access.yml index 68794dc8f..7d55c4948 100644 --- a/rules/windows/sam_database_access.yml +++ b/rules/windows/sam_database_access.yml @@ -34,5 +34,5 @@ where: | ) && oneOf("log.eventDataAccessMask", ["131097", "2032127", "64", "32", "1"]) groupBy: - - origin.host + - adversary.host - target.user diff --git a/rules/windows/sid_history_injection.yml b/rules/windows/sid_history_injection.yml index bcebcc1f6..b1ed4e622 100644 --- a/rules/windows/sid_history_injection.yml +++ b/rules/windows/sid_history_injection.yml @@ -30,5 +30,5 @@ where: | oneOf("log.eventCode", ["4765", "4766"]) && equals("log.channel", "Security") groupBy: - - origin.host + - adversary.host - target.user diff --git a/rules/windows/silver_ticket_detection.yml b/rules/windows/silver_ticket_detection.yml index 8f4e87eb9..9db0d04ad 100644 --- a/rules/windows/silver_ticket_detection.yml +++ b/rules/windows/silver_ticket_detection.yml @@ -30,22 +30,47 @@ description: | 7. Implement AES-only encryption for service accounts 8. Enable Kerberos PAC validation on the targeted services where: | + !oneOf("dataSource", ["", "unknown"]) && + exists("log.authenticationSource") && exists("log.authenticationSourceType") && exists("log.authenticationSourceDomain") && + ( equals("log.eventCode", "4769") && equals("log.channel", "Security") && ( equals("log.eventDataTicketEncryptionType", "23") && !regexMatch("log.eventDataServiceName", "(?i)(krbtgt|\\$$)") && !oneOf("log.eventDataStatus", ["0", "6"]) && - exists("origin.ip") + exists("log.authenticationSource") + ) ) afterEvents: - indexPattern: v11-log-wineventlog-* with: - - field: origin.ip.keyword + - field: dataSource.keyword + operator: filter_term + value: '{{.dataSource}}' + - field: log.authenticationSource.keyword + operator: filter_term + value: '{{.log.authenticationSource}}' + - field: log.authenticationSourceType.keyword + operator: filter_term + value: '{{.log.authenticationSourceType}}' + - field: log.authenticationSourceDomain.keyword + operator: filter_term + value: '{{.log.authenticationSourceDomain}}' + - field: log.authenticationCandidate.silverTicketDetection.keyword + operator: filter_term + value: match + - field: log.eventCode + operator: filter_term + value: '{{.log.eventCode}}' + - field: log.eventDataTicketEncryptionType operator: filter_term - value: '{{.origin.ip}}' + value: '{{.log.eventDataTicketEncryptionType}}' within: 15m count: 5 groupBy: - - origin.ip - - origin.host + - dataSource + - lastEvent.log.authenticationSourceType + - lastEvent.log.authenticationSource + - lastEvent.log.authenticationSourceDomain + - target.user diff --git a/rules/windows/smbv1_usage_detection.yml b/rules/windows/smbv1_usage_detection.yml index 4ebfcf5ff..ebb4547ca 100644 --- a/rules/windows/smbv1_usage_detection.yml +++ b/rules/windows/smbv1_usage_detection.yml @@ -26,5 +26,5 @@ description: | 7. Consider implementing network segmentation to limit exposure if SMBv1 cannot be immediately disabled where: equals("log.eventCode", "3000") && equals("log.providerName", "Microsoft-Windows-SMBServer") && contains("log.message", "SMB1") groupBy: - - origin.host - - origin.ip + - adversary.host + - adversary.ip