Windows Event ID 4625: how to read a failed logon

Every field in a 4625 event, what the status codes mean, and how to tell a user who mistyped their password from a botnet working through a dictionary.

8 min read

What 4625 is

Event ID 4625 — "An account failed to log on" — is written to the Windows Security log every time an authentication attempt is rejected, whatever the reason: wrong password, account that does not exist, account disabled, logon outside permitted hours. Its successful counterpart is 4624.

It is the single most useful event on an internet-facing Windows server, because it is where a brute-force attack becomes visible before it succeeds. Everything a firewall-level defence does begins by reading this stream.

If you see no 4625 events at all on a server you know is exposed, auditing is off rather than the server being quiet. Check and enable it:

cmd
auditpol /get /subcategory:"Logon"
auditpol /set /subcategory:"Logon" /success:enable /failure:enable

The fields that matter

A 4625 record is verbose and most of it is noise. Five fields carry the information:

  • Account Name (under "Account For Which Logon Failed") — the username that was tried. On a real attack this is a parade of names that never existed on your machine.
  • Source Network Address — the IP the attempt came from. This is the field you act on. It is empty for local console logons.
  • Logon Type — how the login was attempted. 10 is RemoteInteractive (RDP), 3 is Network (SMB, or RDP behind some gateways), 2 is a physical console, 5 is a service, 4 is a scheduled task.
  • Failure Reason and Status / Sub Status — the hexadecimal code explaining what was wrong. This is where the useful distinction lives.
  • Workstation Name — the client-supplied hostname. Attack tools often send garbage, a random string, or leave it blank, which is itself a signal.

Status codes, decoded

The Sub Status field is the one to read; the top-level Status is usually the generic 0xC000006D. Learn these five and you can classify almost any 4625 at a glance:

  • 0xC0000064 — the account name does not exist. Almost always an attack: your own users know their own usernames.
  • 0xC000006A — the account exists but the password was wrong. Either a real user fumbling, or a bot that guessed a valid username.
  • 0xC0000234 — the account is locked out. If you see runs of these, your lockout policy is being used against you.
  • 0xC0000072 — the account is disabled. Frequently a dormant account an attacker found and is still trying.
  • 0xC000006F / 0xC0000070 — logon outside permitted hours, or from a workstation that is not allowed. Rare, and worth investigating rather than ignoring.

Telling an attack from a typo

Three signals separate them, and you want all three before you act automatically.

Rate and persistence. A person makes two or three attempts within a minute and then either succeeds or calls you. A bot maintains a steady rate for hours, through the night, without a pause and without ever calling anyone.

Account diversity. A person tries one account: their own. A bot works down a list — administrator, admin, user, test, sql, backup, scan — and most of those accounts do not exist on your machine, which is exactly the 0xC0000064 signature above.

Source. Your people log in from a handful of known addresses. Attacks arrive from hosting ranges, from a different address every few hundred attempts, and often from several countries in the same hour.

This groups the last day's failures by source address and account so all three are visible at once:

powershell
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-1)} |
  ForEach-Object {
    $d = ([xml]$_.ToXml()).Event.EventData.Data
    [pscustomobject]@{
      IP      = ($d | Where-Object Name -eq 'IpAddress')['#text']
      Account = ($d | Where-Object Name -eq 'TargetUserName')['#text']
      Status  = ($d | Where-Object Name -eq 'SubStatus')['#text']
    }
  } |
  Group-Object IP |
  Sort-Object Count -Descending |
  Select-Object -First 15 Count, Name,
    @{n='Accounts'; e={ ($_.Group.Account | Select-Object -Unique) -join ', ' }}

From reading events to blocking them

Reading 4625 by hand is a diagnostic, not a defence — by the time you run the query, the attack has been going for a week. The step that changes anything is doing it continuously and turning a threshold into a firewall rule.

The naive version subscribes to the Security log, counts failures per source, and calls New-NetFirewallRule when a source crosses the line. It works, and then it runs into the problems every such script runs into: the rule set grows into the thousands and the firewall slows to a crawl, the log rotates under load and events are missed, the script dies quietly after a reboot, and one bad match locks the administrator out of the only way in.

RDP Protector runs that loop as a service, with a single consolidated firewall rule instead of thousands, a whitelist that always outranks a block, and the decision made locally so it holds when the network does not. It reads the same event stream shown above — nothing is hidden from you.

FAQ

What is the difference between event 4625 and 4624?
4624 records a successful logon, 4625 a failed one. Both live in the Security log and share the same field layout, so the query above works for either by changing the ID. A 4625 run followed by a 4624 from the same source address is the sequence you never want to see: it means the guessing worked.
What does Logon Type 3 mean in a 4625 event?
Logon Type 3 is a network logon — a connection authenticating over the network rather than at the console. It covers SMB file-share access and, depending on the gateway in front of it, some RDP paths. Direct RDP to the host usually records Logon Type 10 (RemoteInteractive). Seeing large volumes of Type 3 failures usually means SMB is exposed as well as RDP.
Why is the Source Network Address field empty or shown as a dash?
That happens for logons with no network origin — the physical console, some service and scheduled-task logons — and occasionally when the attempt fails so early that the address was not recorded. If the surrounding fields show Logon Type 2, 4 or 5, an empty address is expected and not a sign of anything wrong.
How long does the Security log keep 4625 events?
Until it fills and rotates, which under a real attack can be hours rather than weeks — the default 20 MB fills fast at thousands of events a day. If you are relying on the log for investigation, raise its maximum size or forward events off the host, otherwise the evidence you need will have aged out before you look.

Stop reading the log by hand

The agent watches this exact event stream and turns it into firewall rules, continuously. One server free forever, no card.