Event ID 4625: failed logons explained
What Windows Security Event 4625 means, how to read the logon type and status codes, how to tell brute force from password spraying, and what to do next.
Event ID 4625 is written to the Windows Security log every time an account fails to log on. A handful is normal: people mistype passwords. Dozens in a few minutes, or one source failing against many accounts, is an attack. This guide shows how to read the event and decide quickly.
The fields that matter
| Field | What it tells you |
|---|---|
| Account For Which Logon Failed > Account Name | The account being tried. Ignore the Subject section, which is usually the local system. |
| Logon Type | How the logon was attempted. See the table below. |
| Status / Sub Status | Why it failed. The Sub Status is the precise reason. |
| Source Network Address | The IP the attempt came from. - or 127.0.0.1 means local. |
| Workstation Name | The name the client claimed. Attack tools often send random or blank names. |
| Authentication Package | NTLM or Kerberos. NTLM from unexpected hosts deserves a second look. |
Logon types you will see in 4625
| Type | Meaning | Typical cause of failures |
|---|---|---|
2 | Interactive (keyboard at the machine) | User typo, someone at the console |
3 | Network (SMB, file shares, many RDP attempts with NLA) | Brute force over SMB or RDP, stale mapped drives |
4 | Batch (scheduled task) | A task still running with an old password |
5 | Service | A service account whose password changed |
7 | Unlock | User mistyping at the lock screen |
8 | Network cleartext (IIS basic auth) | Password spraying against web apps |
10 | Remote interactive (RDP without NLA) | RDP brute force from the internet |
11 | Cached interactive | Laptop off the network with an old cached password |
Status and Sub Status codes
| Code | Meaning | Read it as |
|---|---|---|
0xC000006A | Correct username, wrong password | Someone knows a valid account name |
0xC0000064 | Username does not exist | Guessing names: spraying or enumeration |
0xC0000234 | Account is locked out | Attempts continuing after lockout |
0xC0000072 | Account is disabled | Someone trying a leaver's account |
0xC0000071 | Password expired | Usually a real user |
0xC0000193 | Account expired | Contractor or leaver account |
0xC000006F / 0xC0000070 | Outside allowed hours / not allowed from this workstation | Policy working as designed |
0xC000015B | Logon type not granted | Account blocked from RDP or local logon by policy |
0xC0000133 | Clock out of sync with the domain controller | Time sync problem, not an attack |
0xC000006D in the Status field is a generic "bad username or password"; always read the Sub Status for the real reason.
Brute force or password spraying?
- Brute force: one account, many failures, usually from one or a few IPs. Rule of thumb: 10 or more failures for the same account within 10 minutes.
- Password spraying: one source, many different accounts, a few attempts each, often with Sub Status
0xC0000064for names that don't exist. Five or more distinct accounts from one IP is worth an alert. - Misconfiguration: the same account failing every few minutes around the clock, from an internal server, with logon type 4 or 5. That is an old password in a task, service or mapped drive.
The one pattern to never ignore: a run of 4625 failures for an account followed by a 4624 success for the same account from the same source. Treat it as a compromised password until proven otherwise.
Pull the events quickly
PowerShell on the server
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddHours(-24)} |
ForEach-Object {
$x = [xml]$_.ToXml()
$d = @{}; $x.Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' }
[pscustomobject]@{ Time=$_.TimeCreated; User=$d.TargetUserName; IP=$d.IpAddress; Type=$d.LogonType; SubStatus=$d.SubStatus }
} | Group-Object User, IP | Sort-Object Count -Descending | Select-Object -First 20 Count, Name
Microsoft Sentinel (KQL)
SecurityEvent
| where TimeGenerated > ago(24h) and EventID == 4625
| summarize Failures = count(), Accounts = dcount(TargetAccount) by IpAddress, bin(TimeGenerated, 10m)
| where Failures >= 10 or Accounts >= 5
| order by Failures desc
What to do next
- Check for a success. Search 4624 for the same account and IP right after the failures.
- Look up the source IP. Public IP on RDP or SMB means the service is exposed to the internet; that is the real finding.
- Block or restrict. Block the IP at the firewall, put RDP behind a VPN or gateway with MFA, and make sure account lockout policy is set.
- If a success happened: reset the password, sign the user out everywhere, review what the session did (4688 process creation, new services 7045, new users 4720).
- If it is a stale credential: find the task, service or mapped drive on the source host and update it, or the noise will hide a real attack later.
Windows Event Triage flags brute force, password spraying and a success after failures automatically, in your browser.