Most identity threat detection strategies I see are built around the wrong events. Teams collect every security event, build elaborate dashboards showing "Total Logons" and "Failed Password Attempts," and wonder why they're not finding attackers.

Here's the truth: you don't need all the events. You need the right events - the ones that actually indicate identity compromise, privilege escalation, or lateral movement. This post covers the dozen or so event IDs that matter most for identity security, what they mean in real terms, and how to detect the attacks they reveal.

Before You Start: Make Sure the Events Exist

Half of the "we had no alert" findings I write up come down to the event never being generated. Most of the events below need Advanced Audit Policy enabled on your domain controllers (Computer Configuration → Policies → Windows Settings → Security Settings → Advanced Audit Policy Configuration):

All of the PowerShell below reads events by field name rather than by position, which is far less fragile than the Properties[n] indexing you see in most examples. Load this helper first:

# Turn an event into an object with named fields (TargetUserName, IpAddress, ...) function Get-EventData { param([Parameter(ValueFromPipeline)]$Record) process { $xml = [xml]$Record.ToXml() $h = [ordered]@{ TimeCreated = $Record.TimeCreated; Id = $Record.Id } foreach ($d in $xml.Event.EventData.Data) { $h[$d.Name] = $d.'#text' } [pscustomobject]$h } } $since = (Get-Date).AddHours(-24)

These scripts are for triage and threat hunting on a single DC. In production, the same logic belongs in your SIEM, running across every DC.

The Attack Categories

Let me structure this by attack type, not event ID. This is how attackers think - not in terms of Microsoft's event numbering scheme, but in terms of objectives. Your detection should match that.

1. Password Spraying and Brute Force

Event ID 4625 - An account failed to log on (logged on the machine being logged into)
Event ID 4771 - Kerberos pre-authentication failed (logged on DCs)
Event ID 4776 - The computer attempted to validate the credentials for an account (NTLM, logged on DCs)

A spray is one source trying a few passwords against many accounts, which keeps each account under the lockout threshold. So don't count failures per account - count distinct accounts per source:

# One source failing against 10+ different accounts = likely password spray Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625,4771; StartTime=$since} | Get-EventData | Group-Object IpAddress | ForEach-Object { [pscustomobject]@{ Source = $_.Name DistinctUsers = ($_.Group.TargetUserName | Sort-Object -Unique).Count Failures = $_.Count } } | Where-Object { $_.DistinctUsers -ge 10 } | Sort-Object DistinctUsers -Descending

For 4771, failure code 0x18 means a bad password. For 4625, sub-status 0xC000006A is a bad password and 0xC0000064 is a username that doesn't exist - a spray against a list of guessed names produces a lot of the latter.

2. NTLM Use Where It Shouldn't Be

Event ID 4776 is the DC validating an NTLM credential. NTLM is what Pass-the-Hash and NTLM relay rely on, so privileged accounts authenticating with NTLM deserve attention - in a healthy environment, Domain Admins should be using Kerberos almost exclusively.

# NTLM validations for Domain Admins members $privileged = (Get-ADGroupMember 'Domain Admins' -Recursive).SamAccountName Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4776; StartTime=$since} | Get-EventData | Where-Object { $_.TargetUserName -in $privileged } | Select-Object TimeCreated, TargetUserName, Workstation, Status

Don't try to block NTLM on day one - you'll break things. Audit it first (see my Pass-the-Hash post), then restrict it.

3. Kerberoasting

Kerberoasting is when an attacker requests service tickets for accounts with a servicePrincipalName (SPN) and cracks them offline. It needs no privileges - any domain user can do it.

Event ID 4769 - A Kerberos service ticket was requested. This is the event that matters. (The attacker's TGT request, 4768, looks completely normal, so it tells you nothing here.) Look for:

# One account requesting RC4 tickets for 5+ different user-account SPNs Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4769; StartTime=$since} | Get-EventData | Where-Object { $_.TicketEncryptionType -eq '0x17' -and $_.ServiceName -notmatch '\$$' -and $_.ServiceName -ne 'krbtgt' } | Group-Object TargetUserName | ForEach-Object { [pscustomobject]@{ Requester = $_.Name DistinctServices = ($_.Group.ServiceName | Sort-Object -Unique).Count Tickets = $_.Count } } | Where-Object { $_.DistinctServices -ge 5 }

The highest-fidelity Kerberoasting detection is a honeypot. Create a user account with an SPN, a very long random password, and no real use. Nobody should ever request a ticket for it - so any 4769 for that service name is an alert with essentially zero false positives.

4. AS-REP Roasting

AS-REP roasting targets accounts with "Do not require Kerberos pre-authentication" enabled. Anyone can request a TGT for those accounts and crack part of the response offline - no credentials needed at all.

Event ID 4768 - A Kerberos authentication ticket (TGT) was requested. The field to watch is Pre-Authentication Type: a value of 0 means the TGT was issued without pre-authentication.

# Which accounts are exposed? Get-ADUser -Filter 'DoesNotRequirePreAuth -eq $true' -Properties DoesNotRequirePreAuth | Select-Object Name, SamAccountName # TGTs issued without pre-authentication Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4768; StartTime=$since} | Get-EventData | Where-Object { $_.PreAuthType -eq '0' } | Select-Object TimeCreated, TargetUserName, IpAddress, TicketEncryptionType

The better fix is the first query: there are very few legitimate reasons for that flag, so turn pre-authentication back on and the attack goes away.

5. Lateral Movement

Event ID 4624 - An account was successfully logged on (logged on the destination machine). The logon type tells you how:

Because 4624 is written on the destination, spotting one account fanning out across many machines is a SIEM correlation: group network logons by account and count distinct destination hosts over a short window. On a single host, you can hunt for the Pass-the-Hash pattern directly:

# Pass-the-Hash / runas /netonly style logons on this machine Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624; StartTime=$since} | Get-EventData | Where-Object { $_.LogonType -eq '9' -and $_.LogonProcessName -match 'seclogo' } | Select-Object TimeCreated, SubjectUserName, TargetUserName, TargetOutboundUserName

Event ID 4672 - Special privileges assigned to new logon. This fires whenever an account with admin-equivalent privileges (SeDebugPrivilege, SeTcbPrivilege, SeBackupPrivilege and similar) logs on. Its real value is telling you where your privileged accounts are logging on. A Tier 0 account generating 4672 on a workstation or member server is a tiering violation - and exactly how admin credentials end up in LSASS for an attacker to steal.

6. DCSync

DCSync is when an attacker pretends to be a domain controller and asks a real DC to replicate password hashes to it. It needs no code on the DC - just network access to it and the replication rights (Replicating Directory Changes and Replicating Directory Changes All) on the domain.

Event ID 4662 - An operation was performed on an object. The replication rights appear in the event's Properties field as GUIDs, not names, so match on the GUIDs:

# Replication requests from accounts that aren't DCs $repl = '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2|' + # DS-Replication-Get-Changes '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2|' + # DS-Replication-Get-Changes-All '89e95b76-444d-4c62-991a-0facbeda640c' # DS-Replication-Get-Changes-In-Filtered-Set Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4662; StartTime=$since} | Get-EventData | Where-Object { $_.Properties -match $repl -and $_.SubjectUserName -notmatch '\$$' } | Select-Object TimeCreated, SubjectUserName, SubjectDomainName, ObjectName

Domain controllers replicate as their computer accounts (names ending in $), which is why those are excluded. Expect one legitimate hit if you run Entra Connect with password hash sync: the MSOL_ account. Allowlist it explicitly - and treat that server as Tier 0, because that account can DCSync.

7. Privileged Group and Directory Changes

Event IDs 4728, 4732, 4756 - A member was added to a security-enabled global / local / universal group. These are the events for "someone just added themselves to Domain Admins." Domain Admins is a global group (4728); Administrators and Backup Operators are domain local (4732); Enterprise Admins is universal (4756).

# Additions to privileged groups $groups = 'Domain Admins|Enterprise Admins|Schema Admins|Administrators|Account Operators|Backup Operators|Server Operators|DnsAdmins' Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4728,4732,4756; StartTime=$since} | Get-EventData | Where-Object { $_.TargetUserName -match "^($groups)$" } | Select-Object TimeCreated, TargetUserName, MemberName, SubjectUserName

Event ID 5136 - A directory service object was modified. This catches the quieter persistence techniques: permission changes on AdminSDHolder, GPO modifications, and new ACEs on the domain root (for example, an attacker granting themselves DCSync rights).

# Permission changes on AdminSDHolder or the domain root $domainDN = (Get-ADDomain).DistinguishedName Get-WinEvent -FilterHashtable @{LogName='Security'; Id=5136; StartTime=$since} | Get-EventData | Where-Object { $_.AttributeLDAPDisplayName -eq 'nTSecurityDescriptor' -and ($_.ObjectDN -like 'CN=AdminSDHolder,*' -or $_.ObjectDN -eq $domainDN) } | Select-Object TimeCreated, ObjectDN, SubjectUserName, OperationType

My Detection Stack

Here's how I map these into correlation rules:

Attack Type Key Events Correlation Rule
Password Spray 4625, 4771, 4776 One source failing against 10+ distinct accounts within 30 minutes
Kerberoasting 4769 One account requesting RC4 (0x17) tickets for 5+ distinct user-account SPNs within 10 minutes; any request for a honeypot SPN
AS-REP Roasting 4768 Any TGT issued with Pre-Authentication Type 0
DCSync 4662 Replication GUIDs requested by any account that isn't a DC (allowlist the MSOL_ account)
Lateral Movement 4624 (Type 3/10), 4672 One account logging on to 5+ distinct hosts within 10 minutes; Tier 0 accounts logging on outside Tier 0
Pass-the-Hash 4624 (Type 9, seclogo), 4776 NewCredentials logons with seclogo; NTLM validation for privileged accounts
Persistence 4728/4732/4756, 5136 Any privileged group addition outside a change window; any ACL change on AdminSDHolder or the domain root

The thresholds are starting points. Tune them against a couple of weeks of your own baseline before you page anyone at 2 AM.

Final Thought

You don't need to collect every security event. You need the right events, monitored for the right patterns. Start with these and build your detection around actual attack patterns, not Microsoft's internal numbering scheme.

The attackers aren't looking at event IDs - they're thinking about what they want to achieve (credentials, access, persistence). Your detection should mirror that thinking. Identify the attack objectives first, then find the events that reveal those objectives.

Get these monitored correctly and tuned to your environment, and you'll catch the techniques that show up in the majority of identity intrusions - with a lot less noise than a "collect everything" approach.