Skip to content

fix(windows): simplify authentication correlation and repair rules that never fire - #2803

Merged
kryonsx merged 1 commit into
utmstack:v11from
kryonsx:codex/windows-rule-simplify-20260930
Sep 30, 2026
Merged

kryonsx merged 1 commit into
utmstack:v11from
kryonsx:codex/windows-rule-simplify-20260930

Conversation

@kryonsx

@kryonsx kryonsx commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Review of all 48 Windows correlation rules against real logs from 27 v11.2.15 servers. That release runs this exact v11 commit (89cd26c4): the deployed Windows filter and rules are byte-identical. All server access was read-only.

This pull request makes two kinds of fix:

  • It removes checks that add work without changing any result.
  • It repairs rules that could never fire, or that broke when they ran.

1. Authentication correlation: simpler, same or better results

Filter 3.2.2 and seven rules: brute force, failures followed by success, Kerberoasting, AS-REP roasting, silver ticket, golden ticket and AD FS.

  • One source field instead of three. log.authenticationSource stays: the network address, else the workstation, else a domain-qualified account. log.authenticationSourceType and log.authenticationSourceDomain are removed.

    • Over 2 days, 26,418 real source values never appeared with two types.
    • The domain field separated 5 values. Four of those were the same domain written two ways (short name and full name), so it wrongly split one account in two.
  • Two unused markers removed.

    • log.authenticationCandidate.bruteforceAttack only repeated the event code and the presence of source and account, which the search terms already require.
    • ...bruteforceMultipleLogonFailureFollowedBySuccess was written on every successful logon (about 3 million a day) and no rule read it.
    • The Kerberos and AD FS markers stay, because their conditions can't be written as exact search terms. Their history searches drop the terms the marker already implies (event code, encryption type, pre-authentication type).
  • Brute force counts failures per computer and source, whatever account they target. This follows the rule's own description and catches password spraying (one source, many accounts, few tries each). Replaying 2 days of real failures from all servers:

    Design Alerts Worst hour on one server Worst day per computer
    Current (per account) 100 26 32
    This PR (per source) 69 5 7

    One alert per computer and source; repeats are suppressed for 7 days.

  • Failures followed by success skips computer accounts (names ending in $). They are 44% of successful logons, their passwords are machine-generated, and each one used to trigger a history search.

  • Repeated exists(...) checks and a duplicated list value in Kerberoasting are removed.

2. Rules that could never fire, or broke when they ran

Rule Problem Fix
LSASS memory dump handle access Both patterns are invalid in Go (\l, \P), so regexMatch always returned false. Mask 0x120089 was written as 1180185 (that is 0x120219). Valid, case-insensitive patterns; mask 1179785.
Certificate services abuse Reads SubjectUserName, which events 4886/4887 don't have; none of 726 real events had it. Reads log.data.Requester, present on all of them.
AdminSDHolder abuse 4662 names directory objects by GUID: none of 7.2 million real events carried a directory name. 5136 keeps the name in ObjectDN, not ObjectName. 4670 never covers directory objects. Event 5136 with log.data.ObjectDN. SYSTEM is excluded by its SID (S-1-5-18), so translated names are excluded too.
SMBv1 usage Needs log.message, which the Windows agent doesn't send (absent on 26 of 27 servers). Event 3000 from Microsoft-Windows-SMBServer is itself the signal: Microsoft documents that the SMBServer Audit log writes 3000 for each SMBv1 access.
Ransomware mass file writes The history search used target.user, which no 4663 has (0 of 936,902). Every candidate failed with "expression value cannot be nil after placeholder resolution", and after 5 errors the engine switched the rule off. Counts writes by computer, account (origin.user, present on 100%) and access mask.

Alert keys that never existed on these events are replaced with keys that do (SAM, LSASS, ransomware, certificate, SMBv1). Dead code removed:

  • The Sysmon eventCode 1 branches that read NewProcessName. Sysmon uses Image, and no event 1 reached any server.
  • Codes 528/540/673 (Windows 2003 era) and 4769 (which has no logon type) in the loopback Remote Desktop rule.
  • A history block in the NTDS rule that counted any object access on the computer.

Testing

  • Go tests (plugins/alerts, go test ./... passes). 17 new raw fixtures cover every changed behavior, plus a spray case in the history test.
    • Against the unchanged v11 files, the new cases fail for the reasons above: no match for LSASS, certificate, AdminSDHolder and SMBv1; the computer account matches; the spray misses; ransomware raises the nil-placeholder error.
    • With this change, they pass.
  • Local rules lab (EventProcessor 8a3ade7, the same cel.plugin build as the servers; the v11 events and alerts plugins; OpenSearch 2.19.1). Original v11 and this branch were run with the same inputs:
    • 254 records over 3 waves. Real: a brute-force burst, a password spray, a failure-then-success case, certificate requests, loopback Remote Desktop logons, LSASS handle requests, directory changes and Kerberos events. Synthetic, only where no real attack exists: LSASS dump, AdminSDHolder change, SMBv1 access, ransomware writes, computer-account logon.
    • Original: the ransomware rule logged 13 evaluation errors and was switched off, and the spray was missed.
    • This branch: one alert for each expected case, repeats suppressed, no evaluation errors, and every alert key resolved.
    • 1,500 real records (88 event types, 12 servers), compared field by field: the only differences are the removed fields and markers. No other value changed, and neither version raised an alert.
  • Real-data checks with the SDK evaluator:
    • LSASS matches 1 of 2,007 real handle requests with the listed masks: Task Manager, once in September.
    • Certificate matches 19 of 726 real requests, all from computer accounts.

Overlap with #2784

#2784 also touches golden_ticket_detection.yml and the Windows filter. A test merge conflicts only in the golden ticket rule: its version line and its alert keys. There, #2784's deduplicateBy should win. The filter merges cleanly. I will rebase whichever of the two merges second.

Not changed

  • AD FS authentication anomalies still can't fire: no server receives AD FS events, and the rule depends on log.message. Fixing it needs sample 342/516 events, because the event fields aren't documented.
  • Windows audit log was cleared groups by keys event 1102 doesn't have. Every clearing therefore stays its own alert, which suits a rare, important event.
  • Rules already changed in fix(windows): stop four Windows rules from flooding #2784 (log tampering, masquerading, PowerShell obfuscation) are left to that PR.

🤖 Generated with Claude Code

…at never fire

Authentication correlation (filter 3.2.2 and seven rules):
- Keep one derived source, log.authenticationSource. Drop
  log.authenticationSourceType and log.authenticationSourceDomain: on 27
  servers the kind never separated a source, and the domain split one
  account written two ways more often than it separated two accounts.
- Drop the two logon markers. The failed-logon marker repeated the search
  terms; the success marker was written on every successful logon and no
  rule read it. Kerberos and AD FS markers stay; their predicates cannot be
  written as exact terms. Remove the checks and history terms they imply.
- Brute force counts failures per computer and source, whatever account
  they target, so password spraying is caught and one source raises one
  alert. Success after failures skips computer accounts.

Rules that could never fire or broke at runtime:
- LSASS handle access: both patterns were invalid (\l, \P), so the rule
  never matched; access mask 0x120089 was written as 1180185.
- Certificate services: read Requester; SubjectUserName does not exist
  in events 4886 and 4887.
- AdminSDHolder: read the ObjectDN of event 5136; 4662 names objects by
  GUID and 4670 does not cover directory objects.
- SMBv1: event 3000 of the SMB server is the signal; the agent sends no
  message text.
- Ransomware file writes: history searched target.user, which 4663 never
  has, so every candidate failed evaluation and tripped the circuit
  breaker. Count by dataSource, account and access mask.

Alert keys that never resolved now use fields that exist (SAM, LSASS,
ransomware, certificate, SMBv1). Dead branches removed: Sysmon event 1
with NewProcessName, pre-Vista logon codes and 4769 in the loopback
Remote Desktop rule, and an NTDS history block that counted any object
access on the computer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kryonsx
kryonsx marked this pull request as ready for review September 30, 2026 22:27
@kryonsx
kryonsx requested a review from a team September 30, 2026 22:27
@kryonsx
kryonsx merged commit 9a105bc into utmstack:v11 Sep 30, 2026
3 of 7 checks passed
kryonsx added a commit to kryonsx/UTMStack that referenced this pull request Oct 1, 2026
v11 now carries utmstack#2803, which also changed the Golden Ticket rule (v1.0.1)
and the Windows filter (3.2.2). The rule keeps this branch's conditions,
version v1.1.0 and deduplicateBy dataSource, adversary.user on top of
utmstack#2803's simplified authentication-source fields. The filter's Golden
Ticket marker change becomes version 3.2.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant