Back to Blog

Finding What Keeps Locking Out an Active Directory Account

Christopher 6 min read
windowsactive-directorytroubleshooting

An Active Directory account that locks out again minutes after you unlock it usually has a program, service, or saved credential still sending an old password. Windows records where each failed attempt came from, first on the domain controllers and then on the computer that sent it. This post covers how to follow that record from the lockout event to the process that holds the old password.

A recent r/sysadmin thread had a service account for a two-node high availability application that kept locking out. The domain controller logged Kerberos pre-authentication failures (event 4771) for it, and only one of the two nodes triggered them. The search then moves from the domain controller to that node, and the steps below find what on it sends the bad password.

Confirm the lockout on the PDC emulator

Every domain controller forwards a failed password check to the domain controller that holds the PDC emulator role. The PDC emulator keeps the bad password count and locks the account, so I'd start there. Run this from a computer with the Active Directory PowerShell module, with <account> replaced by the account name.

$pdc = (Get-ADDomain).PDCEmulator
Get-ADUser <account> -Server $pdc -Properties LockedOut,BadPwdCount,LastBadPasswordAttempt,AccountLockoutTime | Select-Object Name,LockedOut,BadPwdCount,LastBadPasswordAttempt,AccountLockoutTime

LastBadPasswordAttempt and AccountLockoutTime give you the times to match in every log that follows. When the account locks again within minutes of an unlock, the source is still running, so the events you need are being written while you look.

Read the caller from the lockout event

The PDC emulator writes event 4740 to its Security log when it locks the account. The Caller Computer Name field holds the name of the computer the failed attempt came from.

Get-WinEvent -ComputerName $pdc -FilterHashtable @{LogName='Security'; Id=4740} -MaxEvents 50 | Select-Object TimeCreated,@{n='Account';e={$_.Properties[0].Value}},@{n='CallerComputer';e={$_.Properties[1].Value}}

When Caller Computer Name is a workstation or a member server, go to that computer and skip to the section on finding the process. When it is blank or holds the name of a domain controller, the attempt likely came through a server that passed the credentials on. The authentication events on the domain controllers carry more detail in that case.

Match the Kerberos and NTLM failures

Each domain controller logs the failures it handled itself. Kerberos failures are event 4771, which is written only on domain controllers. Failure Code 0x18 on a 4771 means the wrong password was provided, and Client Address is the IP address of the computer that sent the request. NTLM failures are event 4776 with Error Code 0xC000006A, and its Source Workstation field holds the name of the computer the attempt came from.

Event 4771 needs failure auditing for the Kerberos Authentication Service subcategory, and 4776 needs it for Credential Validation. Check both on one domain controller.

auditpol /get /subcategory:"Kerberos Authentication Service"
auditpol /get /subcategory:"Credential Validation"

Then search every domain controller for both events from the last two hours and keep the ones for the account.

$since = (Get-Date).AddHours(-2)
Get-ADDomainController -Filter * | ForEach-Object { Get-WinEvent -ComputerName $_.HostName -FilterHashtable @{LogName='Security'; Id=4771,4776; StartTime=$since} -ErrorAction SilentlyContinue } | Where-Object Message -match '<account>' | Select-Object MachineName,TimeCreated,Id

Open the matching events in Event Viewer on each domain controller to read Client Address or Source Workstation. A Client Address that starts with ::ffff: is an IPv4 address, so resolve the part after it with Resolve-DnsName to get the computer name.

When the domain controllers only point at another server

When the source is a server that passes sign-ins on to the domain controllers, the Netlogon debug log on the PDC emulator records each forwarded attempt with the computer it came from. Turn it on from an elevated prompt on the PDC emulator.

Nltest /DBFlag:2080FFFF

Let it run for 30 to 60 minutes while the lockouts continue. The log is %windir%\debug\netlogon.log, and it can't be read while Netlogon writes to it. Copy it to another folder and search the copy for 0xC000006A, the status for a bad password.

type netlogon.log | find /i "0xC000006A" > badpassword.txt

Each matching line holds the account name and the name of the computer the attempt came from. Turn the debug log back off when you have the computer name.

Nltest /DBFlag:0x0

Find the process on the source computer

The failed sign-in is also written on the computer where it was attempted, and that copy records which program made it. Turn on failure auditing for logons there from an elevated prompt.

auditpol /set /subcategory:"Logon" /failure:enable

At the next lockout, find event 4625 in that computer's Security log at the same times as the failures on the domain controllers.

Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625,4648} -MaxEvents 50 | Select-Object TimeCreated,Id,Message | Format-List

Sub Status 0xC000006A on a 4625 means the password was wrong. Caller Process Name holds the full path of the program that tried to sign in. Logon Type 4 is a batch sign-in such as a scheduled task, and Logon Type 5 is a service started by the Service Control Manager.

When no 4625 matches, look at event 4648 at the same times. It records a sign-in with explicit credentials, and its Process Name field holds the program that supplied them. A Caller Process Name of lsass.exe with Logon Type 3 means the attempt came in over the network. Read Source Network Address and Workstation Name on that event and repeat this step on that computer.

Check what still holds the old password

On the computer where the record ends, list the services and scheduled tasks that run as the account.

Get-CimInstance Win32_Service | Where-Object StartName -like '*<account>*' | Select-Object Name,StartName,State
schtasks /query /v /fo csv | ConvertFrom-Csv | Where-Object 'Run As User' -like '*<account>*' | Select-Object TaskName,'Run As User'

Update the stored password on each service and task, then restart the service. When the account belongs to a person, also check the credentials saved in their Windows profile with cmdkey /list while signed in as them, drives they mapped with saved credentials with Get-SmbMapping, and sessions they left signed in on other servers with quser /server:<name>. When none of these match, the application likely makes the sign-in itself with a password saved in its own settings, so update the password there.

Unlock the account after the last source is fixed, then check the PDC emulator for a new 4740 the next day.

Where ET Ducky shows it

I build ET Ducky, and its Windows agent reads the kernel event stream through ETW on every managed computer, including process creation and the outbound connections each process opens. You can open a Live Session shell from the browser without the need for a VPN and run the auditpol and Get-WinEvent commands above on the node or domain controller you're tracing.

You'd start a Multi-Agent Session across the node that triggers the lockouts and the node that doesn't. It compares the kernel events from both to find the outlier. Only counts-only summaries leave each host, and the raw kernel events stay on it.

Try it on one server

One Windows or Linux server on the Free query tier with your own AI key costs $5 a month.

See pricing