Detect Windows Password Spraying with Event ID 4625
MITRE ATT&CK: T1110.003T1110.001T1078.002
Sigma Rule
title: Windows Password Spraying via Failed Logon (4625)
id: c47b2e91-5a83-4d16-9f27-8b3e1c6a049d
status: experimental
description: >
Detects password spraying and brute force against Windows or Active Directory.
Event ID 4625 is a failed logon; the SubStatus code distinguishes a bad password
(0xC000006A) from a non-existent account (0xC0000064), which separates spraying
from username enumeration. LogonType 3 indicates the attempt arrived over the
network rather than at the console.
references:
- https://attack.mitre.org/techniques/T1110/003/
- https://attack.mitre.org/techniques/T1078/002/
author: LogTriage
date: 2026/08/04
logsource:
product: windows
service: security
detection:
selection:
EventID: 4625
LogonType: 3
auth_failure:
# 0xC000006A bad password (valid user) | 0xC0000064 user does not exist
# 0xC0000072 account disabled but real
SubStatus:
- '0xC000006A'
- '0xC0000064'
- '0xC0000072'
timeframe: 15m
condition: selection and auth_failure | count(TargetUserName) by IpAddress >= 8
falsepositives:
- Service account with a stale password in a scheduled task or app pool
- Stale cached credential on a phone or laptop after a password change
- Health probe or load balancer authenticating with an expired credential
level: high
tags:
- attack.credential_access
- attack.t1110.003
- attack.t1078.002
What this rule detects
Event ID 4625 is a failed logon on Windows. A handful is normal; a burst is not. This rule targets the pattern that matters most in Active Directory: password spraying, where an attacker tries one or two common passwords against many accounts, specifically to stay under the per-account lockout threshold.
The rule fires when a single source IP produces failed logons against 8 or more distinct usernames within 15 minutes.
Detection logic
The core selection is EventID: 4625 with LogonType: 3 — a failed network logon. Console
mistyping (type 2) and cached credentials (type 11) are excluded because they are dominated by
genuine user error.
The auth_failure selection then requires one of three SubStatus codes. They are grouped into a
single selection because any of them confirms a real authentication failure, but each tells you
something different about what the attacker is learning:
| SubStatus | Meaning | What it tells you |
|---|---|---|
0xC000006A | Bad password, valid username | Spraying against confirmed-real accounts |
0xC0000064 | Username does not exist | Enumeration — working a wordlist |
0xC0000072 | Account disabled but real | Found a stale account; may pivot to others |
The aggregation is the important part: count distinct TargetUserName per IpAddress, not
failures per account. Spraying is engineered so no single account accumulates enough failures to
lock out — a per-account rule is blind to it by construction. Counting breadth rather than
depth is what makes the attack visible.
Validated against a real sample
Validated against windows_brute_force.xml (shipped with LogTriage): repeated 4625 events
from 185.220.101.45 against DC01.contoso.com, targeting administrator and other accounts
in the CONTOSO domain, with Status 0xC000006D / SubStatus 0xC000006A and LogonType 3 —
network-delivered password guessing against valid accounts on a domain controller.
The rule fires on that sample and stays silent on the matching benign log, which contains only
4624 (successful logon), 4672 (special privileges), 4688 (process creation), 4768 and
4776 — no 4625 events at all.
False positives
Almost all real-world 4625 noise is a stale credential, not an attack, and it has a distinctive shape: very high volume against a single account from a single host, repeating indefinitely. Scheduled tasks, IIS application pools, and service accounts after a password rotation are the classic sources.
Because this rule keys on the number of distinct usernames, that noise does not trigger it — one account repeated 500 times still counts as one username. That is the main reason to count breadth rather than volume.
Frequently Asked Questions
- What is the difference between password spraying and brute force here?
- Brute force is many passwords against one account; spraying is one common password against many accounts. Spraying is the more dangerous pattern because it stays under per-account lockout thresholds by design — no single account sees enough failures to lock. That is why this rule counts distinct TargetUserName values per source IP rather than counting failures per account.
- Why does the SubStatus code matter so much?
- It tells you what the attacker learned. 0xC000006A means the username was valid but the password was wrong — the account exists, which is useful intelligence to them. 0xC0000064 means the username does not exist, indicating enumeration rather than spraying. 0xC0000072 means the account is disabled but real. A run of 0xC000006A across many accounts is spraying against confirmed-valid users, which is the highest-severity variant.
- Why filter on LogonType 3?
- LogonType 3 is a network logon — SMB, WinRM, LDAP bind, or an authenticated web request. That is how remote attacks arrive. LogonType 2 (interactive console) and 11 (cached) are usually a user at a keyboard genuinely mistyping. Filtering to type 3 removes most benign mistyping while keeping every remotely delivered attempt.
- Does this catch Kerberoasting or AS-REP roasting?
- No — those are different techniques with their own event IDs (4769 and 4768 with specific encryption types). This rule targets password guessing only. Roasting attacks request tickets rather than failing logons, so they generate successful events, not 4625s, and need a separate detection.
Related Resources
See this detection run on a real report
Try the live demo with a pre-loaded malicious log set — no signup required — or upload your own log file and get a full AI-reviewed threat report in minutes.