Potřebujete vědět, jaké se vám zamykají účty (account lockout) v Active Directory a kdy se to stalo? Pokud auditujete Account Management - User Account Management, tak se vám budou v protokolu událostí Security na řadičích domény (DC - domain controller) objevovat události EventId 4740.
Ideální je to sledovat na FSMO PDC. Je sice pravděpodobné, že klient se snaží ověřovat o jiné DC. Ale jak známo, pokud na některém DC selže ověření, toto DC zkusí ověřovací pokus samo přeposlat na PDC. Jestli by se to náhodou nepovedlo.
Přeposílat pokusy na PDC má smysl ze dvou důvodů. Možná se jedná jen o nové heslo, které si uživatel nedávno změnil. Nebo je to sice opravdu špatné heslo, ale kvůli tomu, že badPasswordCount atribut je nereplikovaný, je potřeba zajistit zamknutí účtu.
Takže velkou většinu nepovedných přihlášení procesuje PDC. Samozřejmě ne úplně všechny, protože přeposílka je jenom best-effort a pokud nějaké DC nemá zrovna konektivitu s PDC, tak se to nijak nehlásí ani neprojevuje. Ale ona ta konektivita dlouhodobě existuje, aspoň doufejte, jinak vám nebude fungovat zamykací politika (lockout policy).
A jak to z toho logu s miliony událostí dostanete? Pomocí wevtutil, XPath a PowerShellu:
# Note: although the standard XPath does not make difference between single-quotes and double-quotes
# this particular incarnation requires use of single-quotes inside the XPath queries, so use double-quotes
# in PowerShell to enclose the string literal.
# Note: removal of the stupid namespace xmlns='http://schemas.microsoft.com/win/2004/08/events/event' is the simplest
# means to make the following SelectSingleNode() API to work easily.
$all = wevtutil qe Security "/q:*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and (Task=13824) and (EventID=4740)]]" | % { $evt = [XML] $_.Replace(" xmlns='http://schemas.microsoft.com/win/2004/08/events/event'", ''); $evt } | select @{ n = 'Id' ; e = { $_.Event.System.EventId } }, @{ n = 'User' ; e = { $_.Event.EventData.SelectSingleNode('./Data[@Name="TargetUserName"]').'#text' } }, @{ n = 'When' ; e = { [DateTime]::Parse($_.Event.System.TimeCreated.SystemTime) } }
Tohle byl jednoduchý skript. Jenže my bychom ještě chtěli vědět, odkud se ty zámky dějí. Kdo odkud zkouší ta hesla.
K tomu potřebujete další události typu Account Logon. Události Account Logon jsou dvou druhů. Buď od ověření pomocí Kerberos, nebo NTLM. Kerberos generuje pod-kategorii Kerberos Authentication Service, zatímco NTLM generuje události se subcategory Credentials Validation.
Stačí tedy najít těch několik Account Logon událostí, které předcházely zamknutí účtu a z nich zjistit, co se dělo. Kerberos krásně loguje IP adresu, ze které k pokusu o ověření došlo. NTLM neloguje nic. NTLM sice loguje jméno počítače, ze kterého k pokusu došlo, tedy nikoliv IP adresu, ale tato hodnota je nesmyslná, protože není vůbec ověřovaná.
Pokud se tedy jedná o opravdový pokus nějakého normálního doménového počítače o ověření, tak tam bude jeho jméno v pořádku. Ale pokud to je útočný program, který testuje hesla přes NTLM, tak tam nebude buď nic, nebo nějaký nesmysl. I tak nám ale stojí za to vědět alespoň, jestli se jednalo o NTLM, nebo Kerberos.
Lepší přehled dostanete z lepšího skriptu. Je tam počet těch předchozích událostí během předchozích 6 minut. A je tam také seznam těch zdrojových IP adres (v případě Kerberosu) nebo údajných jmen počítačů v případě NTLM.
Pokud budete mít smůlu a bude se jednat jen o NTLM ověření, bez informace o zdroji, tak to máte horší. Musíte si pak spustit třeba Wireshark, nebo Network Monitor a zkustit tam to TCP spojení najít.
# Note: User Account Management - Account Locked Out
# Note: before these, if this is the originating DC (on PDC if the authentication attempt is only forwarded, the PDC does not log the previous Account Logon failures)
# there would be either:
# Account Logon - Credential Validation (Task 14336) - 4776 - TargetUserName, Workstation, Status = 0xC000006A
# Account Logon - Kerberos Authentication Service (Task 14339) - 4771 - TargetUserName, IpAddress, Status = 0x18
$authFailuresPastMinutes = 6
[object[]] $allLocks = wevtutil qe Security "/query:*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and (Task=13824) and (EventID=4740)]]" | % { $evt = [XML] $_.Replace(" xmlns='http://schemas.microsoft.com/win/2004/08/events/event'", ''); $evt } | select @{ n = 'Id' ; e = { $_.Event.System.EventId } }, @{ n = 'What' ; e = { 'Lockout' } }, @{ n = 'User' ; e = { $_.Event.EventData.SelectSingleNode('./Data[@Name="TargetUserName"]').'#text' } }, @{ n = 'When' ; e = { [DateTime]::Parse($_.Event.System.TimeCreated.SystemTime) } } | sort -Desc When
[Collections.ArrayList] $authFailures = @()
foreach ($oneLock in $allLocks) {
$dateTimeSortableEventUTC = 'yyyy-MM-ddTHH:mm:ss.fffffff00Z'
$failureFilter = "*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and ((Task=14336 and EventID=4776) or (Task=14339 and EventID=4771)) and TimeCreated[@SystemTime<='{1}' and @SystemTime>='{2}']] and EventData/Data[@Name='TargetUserName']='{0}' and (EventData/Data[@Name='Status']='0xC000006A' or EventData/Data[@Name='Status']='0x18')]" -f $oneLock.User, $oneLock.When.ToUniversalTime().ToString($dateTimeSortableEventUTC), $oneLock.When.AddMinutes(-$authFailuresPastMinutes).ToUniversalTime().ToString($dateTimeSortableEventUTC)
[object[]] $oneLockAuthFailures = wevtutil qe Security /query:$failureFilter | % { $evt = [XML] $_.Replace(" xmlns='http://schemas.microsoft.com/win/2004/08/events/event'", ''); $evt } | select @{ n = 'Id' ; e = { $_.Event.System.EventId } }, @{ n = 'What' ; e = { 'AuthFailure' } }, @{ n = 'User' ; e = { $_.Event.EventData.SelectSingleNode('./Data[@Name="TargetUserName"]').'#text' } }, @{ n = 'When' ; e = { [DateTime]::Parse($_.Event.System.TimeCreated.SystemTime) } }, @{ n = 'From' ; e = { if ($_.Event.System.EventID -eq 4776) { $_.Event.EventData.SelectSingleNode('./Data[@Name="Workstation"]').'#text' } else { $_.Event.EventData.SelectSingleNode('./Data[@Name="IpAddress"]').'#text' } } }
[void] $authFailures.AddRange($oneLockAuthFailures)
$countNTLM = 0
$countKerberos = 0
[Collections.ArrayList] $sources = @()
foreach ($oneAuthFailure in $oneLockAuthFailures) {
if ($oneAuthFailure.Id -eq 4776) {
$countNTLM ++
} else {
$countKerberos ++
}
[string] $source = $oneAuthFailure.From
if ([string]::IsNullOrEmpty($source)) {
$source = '-'
}
if ($source.StartsWith('::ffff:')) {
$source = $source.Substring(7)
}
if ($sources -notcontains $source) {
[void] $sources.Add($source)
}
}
Add-Member -Input $oneLock -MemberType NoteProperty -Name NtlmFailures -Value $countNTLM
Add-Member -Input $oneLock -MemberType NoteProperty -Name KerberosFailures -Value $countKerberos
Add-Member -Input $oneLock -MemberType NoteProperty -Name From -Value ($sources -join ',')
$oneLock | ft -Auto
}
# $allLocks # contains all the locking statistics
# $authFailures # contains all the authentication failure events that occured several minutes before the relevant account lockout
Pro úplnost bych ještě doplnil informaci, že na PDC se nelogují události Account Logon, pokud se jedná o tu přeposílku z jiného DC. Je tedy možné, že počet předchozích neúspěšných pokusů na heslo může být i nulový.