0xC000006D Windows logon failure code Triage: medium

0xC000006D: Windows Logon Failure — Bad Username or Password

Published

The logon was rejected as invalid without saying which half was wrong — it is the general failure result that event 4625 usually carries in Status, with the real reason in Sub Status.

Did the attacker have the password? No — the password (or username) was wrong.

Microsoft's definition

STATUS_LOGON_FAILURE

The attempted logon is invalid. This is either due to a bad username or authentication information.

Microsoft's 4625 monitoring note: "This is either due to a bad username or authentication information" for critical accounts or service accounts. Especially watch for a number of such events in a row.

Source: [MS-ERREF] NTSTATUS Values · checked against the published table on every build

Where it appears

  • Event 4625 (An account failed to log on): the Status or Sub Status field
  • Event 4776 (credential validation, NTLM) on domain controllers: the Error Code field
  • Microsoft Sentinel / Log Analytics: the Status and SubStatus columns of the SecurityEvent table

What it means

Event 4625 records two status fields. Status is the general result of the logon attempt and Sub Status is the specific reason. 0xC000006D — STATUS_LOGON_FAILURE — is the generic “this logon is invalid” result, and it is the value you will see in Status for the great majority of password-related 4625 events.

Authentication protocols avoid telling a caller whether the username or the password was wrong, which is exactly what the official wording reflects. Windows keeps the detail for the local log, in Sub Status:

StatusSub StatusReading
0xC000006D0xC0000064The account name does not exist
0xC000006D0xC000006AThe account exists, the password was wrong
0xC000006D0xC000006D or missingThe event does not say which failed

Account-state rejections normally arrive under a different Status, 0xC000006E, with the reason in Sub Status — for example 0xC0000072 for a disabled account or 0xC0000071 for an expired password. A lockout surfaces as 0xC0000234. So a plain 0xC000006D tells you the credential itself was refused, not that the account is in a restricted state.

Domain controllers can also record 0xC000006D as the Error Code of event 4776 when they validate NTLM credentials.

Is it an attack?

Two other fields decide this: Logon Type (3 is network, including SMB and RDP attempts with Network Level Authentication; 10 is RemoteInteractive; 2 is at the console) and the source address.

  • One or two failures for one user, then a 4624 success from the same workstation — a typo.
  • Regular failures every few minutes from one of your own servers — a stored credential in a service, scheduled task or mapped drive that was never updated after a password change. The timing is machine-like and the account usually changed its password recently.
  • Dozens of failures for one account from a single source in minutes — brute force, often ending in a lockout.
  • One source failing against many different account names in an hour — password spraying. A spray typically mixes real accounts (Sub Status 0xC000006A) with invented names (0xC0000064); the Windows 4625 password-spray rule groups exactly that way.
  • External address, logon type 3 or 10, administrator-like names — internet-exposed RDP or SMB under attack.

How to investigate

Start by grouping the general failures by source and seeing which sub-status reasons each source produced:

SecurityEvent
| where TimeGenerated > ago(1d)
| where EventID == 4625 and Status =~ "0xc000006d"
| summarize Failures = count(), Accounts = dcount(TargetUserName),
            AccountList = make_set(TargetUserName, 20),
            Reasons = make_set(SubStatus, 10), LogonTypes = make_set(LogonType, 5)
    by IpAddress, WorkstationName, Computer
| order by Failures desc

A source with many accounts and a wide spread of reasons is enumerating; a source with one account and one reason is grinding a single password.

Then answer the question that changes the severity — did the source eventually get in?

let failures = SecurityEvent
    | where TimeGenerated > ago(1d) and EventID == 4625 and Status =~ "0xc000006d"
    | summarize FailCount = count(), LastFail = max(TimeGenerated) by IpAddress, TargetUserName;
SecurityEvent
| where TimeGenerated > ago(1d) and EventID == 4624
| join kind=inner failures on IpAddress, TargetUserName
| where TimeGenerated > LastFail
| project TimeGenerated, Computer, TargetUserName, TargetDomainName, IpAddress, LogonType, FailCount

Any success after a run of failures from the same source is a compromise until proven otherwise: reset the account, isolate the source if it is internal, and review what the session touched. If the failures turn out to be a service with a stale password, fix the credential rather than suppressing the alert — the noise hides real spraying against the same host. Without a SIEM, export the Security log from Event Viewer as XML and load it into the free browser analyzer, which groups 4625 events by source and Sub Status.

Related codes

  • 0xC000006A STATUS_WRONG_PASSWORD The account exists and the password was wrong. One event is a typo; many against one account is brute force; one source failing across many real accounts is password spraying.
  • 0xC0000064 STATUS_NO_SUCH_USER Someone tried to log on with an account name that does not exist. Occasional typos are normal; bursts of invented names from one source are username guessing or spraying with a generic list.
  • 0xC000006E STATUS_ACCOUNT_RESTRICTION
  • 0xC0000234 STATUS_ACCOUNT_LOCKED_OUT The account is locked because the lockout threshold was reached; every further attempt fails with this code until the lockout ends or an administrator unlocks it.
  • 0xC0000072 STATUS_ACCOUNT_DISABLED
  • 0xC0000071 STATUS_PASSWORD_EXPIRED

Detect it

MITRE ATT&CK: T1110.001T1110.003

Frequently Asked Questions

What is the difference between Status 0xC000006D and Sub Status 0xC000006D?
Status holds the general outcome of the logon and Sub Status holds the specific reason. In most 4625 events Status is 0xC000006D and Sub Status carries the detail — 0xC0000064 for a name that does not exist, 0xC000006A for a wrong password on a real account. When Sub Status repeats 0xC000006D, or is absent, the event is not telling you which half failed and you have to infer it from the surrounding events.
Does 0xC000006D mean the account exists?
On its own, no. Microsoft's description covers both a bad username and bad authentication information, so the code deliberately does not distinguish them. Read the Sub Status in the same event: 0xC0000064 means the name did not resolve to an account, while 0xC000006A means the name matched and only the password was wrong.
Why do I see 4625 events with Status 0xC000006D and no useful Sub Status?
Some authentication paths and third-party or non-domain clients produce 4625 records where the sub-status detail is missing or just repeats the general failure. Treat those events by shape rather than by code — count attempts per source address, per account and per logon type, and check whether a 4624 success follows from the same source.
How many 0xC000006D failures are worth alerting on?
There is no vendor-published number. The useful shapes are the same as for any credential attack — dozens of failures against one account from one source within minutes is brute force, and one source failing against many different accounts in an hour is spraying. Service accounts and administrator accounts deserve a lower threshold because they should not be mistyping passwords at all.

Find 0xC000006D in your own logs

Load a Windows Security event export into the free browser analyzer to group failures by account, source IP and status code (nothing is uploaded), or run it through LogTriage for threat-intel enrichment and an AI-written incident report.