0x12 Kerberos failure code Triage: medium

0x12: Kerberos Failure KDC_ERR_CLIENT_REVOKED — Disabled or Locked Account

Published

The domain controller refused the ticket request because the account itself is barred from logging on — Microsoft's examples are an account that is disabled, expired or locked out — rather than because of anything about the password.

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

Microsoft's definition

KDC_ERR_CLIENT_REVOKED

Clients credentials have been revoked

Source: Microsoft event 4771 Kerberos error codes · checked against the published table on every build · look up another code

Where it appears

  • Event 4771 (Kerberos pre-authentication failed) on domain controllers: the Failure Code field
  • Event 4768 (A Kerberos authentication ticket (TGT) was requested) on domain controllers: the Result Code field
  • Microsoft Sentinel / Log Analytics: the Status column of the SecurityEvent table (EventID 4771 or 4768)

What it means

Failure code 0x12, KDC_ERR_CLIENT_REVOKED, is described in Microsoft’s table for event 4771 as “Clients credentials have been revoked”. The KDC found the principal, decided the account is not permitted to obtain tickets at all, and refused the request. Microsoft’s 4768 table explains it as “This might be because of an explicit disabling or because of other restrictions in place on the account. For example: account disabled, expired, or locked out.” The event itself does not say which restriction applies — you have to look at the account object in Active Directory. (Revoked smart-card certificates are a different code: Microsoft points to 0x3E for those.)

Microsoft’s documentation for both 4768 and 4771 states that “This event generates only on domain controllers”, so collect these from your DCs. The fields that matter for triage are Account Name (TargetUserName) and Client Address, which Microsoft describes as the “IP address of the computer from which the TGT request was received”, written as an IPv6 or IPv4 address, as ::ffff:IPv4_address, or ::1 for localhost — strip the ::ffff: prefix before comparing it against your IPv4 inventory. ServiceName (typically krbtgt/DOMAIN), IpPort and the DC name in Computer complete the picture.

Is it an attack?

PatternReading
0x12 for one account every few minutes from one internal host, evenly spacedA stored credential — service, scheduled task, mapped drive or mail client — still retrying after the account was disabled or locked
A burst of 0x18 for an account, then 0x12 from the same client addressBrute force that hit the lockout threshold; the matching NTLM/interactive code is 0xC0000234
0x12 for a leaver’s account or a retired service account, from an unfamiliar or external addressSomeone is using a credential they should not have; treat as an incident
One client address, 0x12 across many distinct accounts, mixed with 0x6A list-driven attack working through names that include disabled and nonexistent accounts

Because this traffic goes to the KDC, it does not have to touch a member server and may never raise a 4625. Pair a 4771/4768 view of the domain controllers with endpoint-based detections such as the Windows 4625 password-spray rule.

How to investigate

Start with who is generating the code and how broadly:

SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID in (4768, 4771) and Status =~ "0x12"
| extend ClientIp = replace_string(IpAddress, "::ffff:", "")
| summarize Events = count(), Accounts = dcount(TargetUserName),
            AccountList = make_set(TargetUserName, 20),
            FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
    by ClientIp, Computer, EventID
| order by Accounts desc, Events desc

Then separate stale credentials from attacks by checking whether the same source also failed pre-authentication or asked for names that do not exist:

SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID in (4768, 4771) and Status in~ ("0x12", "0x18", "0x6")
| extend ClientIp = replace_string(IpAddress, "::ffff:", "")
| summarize Revoked = countif(Status =~ "0x12"),
            PreAuthFailed = countif(Status =~ "0x18"),
            NoSuchUser = countif(Status =~ "0x6"),
            Accounts = dcount(TargetUserName)
    by ClientIp
| where Revoked > 0 and (PreAuthFailed > 0 or NoSuchUser > 0)
| order by Accounts desc

Finally, check whether any of those accounts later obtained a TGT from the same address — a success after the account was unlocked or re-enabled changes the severity:

let revoked = SecurityEvent
    | where TimeGenerated > ago(7d) and EventID in (4768, 4771) and Status =~ "0x12"
    | extend ClientIp = replace_string(IpAddress, "::ffff:", "")
    | summarize RevokedCount = count(), LastRevoked = max(TimeGenerated) by TargetUserName, ClientIp;
SecurityEvent
| where TimeGenerated > ago(7d) and EventID == 4768 and Status =~ "0x0"
| extend ClientIp = replace_string(IpAddress, "::ffff:", "")
| join kind=inner revoked on TargetUserName, ClientIp
| where TimeGenerated > LastRevoked
| project TimeGenerated, Computer, TargetUserName, ClientIp, IpPort, ServiceName, RevokedCount

If the source is internal and the cadence is regular, hunt for the stored credential on that host and fix it rather than reopening the account. If the source is unfamiliar, keep the account disabled, identify the process making the requests, and review any service tickets (event 4769) requested from the same address. Exported Security logs can be grouped by account and client address in the free browser log analyzer.

Related codes

  • 0x6 KDC_ERR_C_PRINCIPAL_UNKNOWN The domain controller could not find the requested account in its database — the name does not exist, and a run of these from one source is account enumeration.
  • 0x17 KDC_ERR_KEY_EXPIRED The domain controller refused the ticket request because the account's password has expired and has to be changed before Kerberos will issue a ticket — routine for people, but a problem worth chasing when it hits service accounts or comes from an address you do not recognise.
  • 0x18 KDC_ERR_PREAUTH_FAILED The domain controller rejected the pre-authentication data sent with a ticket request — Microsoft's listed cause is that the wrong password was provided; repeated 0x18 events for one account are a brute-force indicator.
  • 0x20 KRB_AP_ERR_TKT_EXPIRED

Detect it

MITRE ATT&CK: T1110.003T1078.002

Frequently Asked Questions

Does 0x12 tell me whether the password was correct?
No. The code reports the state of the account, not the outcome of credential checking, and the event does not record whether the pre-authentication data would have validated. To judge that, look at the same account and client address before the lockout or disable — a run of 0x18 (pre-authentication failed) immediately before the 0x12 events means someone was guessing the password and tripped the lockout threshold.
Which event carries 0x12 — 4768 or 4771?
Either, so hunt across both. Microsoft's 4768 documentation lists 0x12 among that event's result codes and the 4771 table includes it too; only 0x10 and 0x18 are excluded from 4768, which Microsoft says "doesn't generate for Result Codes: 0x10 and 0x18". Both events are recorded only on domain controllers.
Why does one account produce 0x12 over and over from the same host?
Because whatever holds the credential keeps retrying after the account was disabled or locked. Scheduled tasks, Windows services, mapped drives, saved RDP sessions and mobile mail clients retry on a fixed interval, which produces a steady, evenly spaced stream of 0x12 from one known internal address against one account.
Is 0x12 against a disabled account worth an incident?
It is worth checking every time. A disabled leaver account or a decommissioned service account being presented to the KDC means something still holds that credential. If the client address is unfamiliar, external, or a host the account had no business on, treat it as an intrusion lead and identify the process on the source machine before closing it as noise.

Find 0x12 in your own logs

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