Active Directory Advanced Threat Hunting - Tracing the cause of account lockouts and password errors

MVP

 

Dear Microsoft Active Directory friends,

 

In this article we are going on a "search for clues" :-). In the life of an IT administrator, you have certainly often had to reset a user's password or remove an account lockout.

 

Now the question arises on which system the account was locked or on which system the password was entered incorrectly.

 

In order to determine this information with PowerShell, some preparations must be made. "Advanced Audit Policy Configuration" must be configured in the group policies.

 

This article from Microsoft provides a good starting point:
https://learn.microsoft.com/en-us/defender-for-identity/deploy/event-collection-overview


In my example, I have adapted the Default Domain Controls Policy.

AD_Advanced_Audit.png

 

Before we begin, here is some important information about MITRE techniques:

 

Account Access Removal:
https://attack.mitre.org/techniques/T1531/

 

User Account:
https://attack.mitre.org/datasources/DS0002/

 

Brute Force: Password Spraying:
https://attack.mitre.org/techniques/T1110/003/

 

Account lockouts are logged in the Windows event logs with the ID 4740. We will therefore focus on this event ID first.

 

The start of the PowerShell script looks like this:

 

#Prep work for lockouts, Account lockout Event ID
$LockOutID = 4740

 

#Find the PDC
(Get-ADDomain).PDCEmulator
$PDCEmulator = (Get-ADDomain).PDCEmulator

 

#Connect to the PDC
Enter-PSSession -ComputerName $PDCEmulator

 

#Query event log
Get-WinEvent -ComputerName $PDCEmulator -FilterHashtable @{
LogName = 'Security'
ID = $LockOutID
}

 

#Parse the event and assign to a variable
$events = Get-WinEvent -ComputerName $PDCEmulator -FilterHashtable @{
LogName = 'Security'
ID = $LockOutID
}

 

#Examine some properties
$events[0].Message

 

#Regex?
$events[0].Message -match 'Caller Computer Name:\s+(?<caller>[^\s]+)'
$Matches.caller

 

#Cool, but not as easy as:
$events[0].Properties
$events[0].Properties[1].Value

 

#For all events:
ForEach($event in $events){
[pscustomobject]@{
UserName = $event.Properties[0].Value
CallerComputer = $event.Properties[1].Value
TimeStamp = $event.TimeCreated
}
}

 

#And we'll make that a function
Function Get-ADUserLockouts {
[CmdletBinding(
DefaultParameterSetName = 'All'
)]
Param (
[Parameter(
ValueFromPipeline = $true,
ParameterSetName = 'ByUser'
)]
[Microsoft.ActiveDirectory.Management.ADUser]$Identity
)
Begin{
$LockOutID = 4740
$PDCEmulator = (Get-ADDomain).PDCEmulator
}
Process {
If($PSCmdlet.ParameterSetName -eq 'All'){
#Query event log
$events = Get-WinEvent -ComputerName $PDCEmulator -FilterHashtable @{
LogName = 'Security'
ID = $LockOutID
}
}ElseIf($PSCmdlet.ParameterSetName -eq 'ByUser'){
$user = Get-ADUser $Identity
#Query event log
$events = Get-WinEvent -ComputerName $PDCEmulator -FilterHashtable @{
LogName = 'Security'
ID = $LockOutID
} | Where-Object {$_.Properties[0].Value -eq $user.SamAccountName}
}
ForEach($event in $events){
[pscustomobject]@{
UserName = $event.Properties[0].Value
CallerComputer = $event.Properties[1].Value
TimeStamp = $event.TimeCreated
}
}
}
End{}
}

 

#Usage
Get-ADUserLockouts

Lockout_BadPW.JPG

 

#Single user
Get-ADUser 'jesse.pinkman' | Get-ADUserLockouts

 

Now we come to the incorrectly entered passwords. These events are logged in the Windows event logs with the ID 4625.

 

#Prep work for bad passwords - Event ID
$badPwId = 4625

 

#Get the events from the PDC
$events = Get-WinEvent -ComputerName $PDCEmulator -FilterHashtable @{
LogName = 'Security'
ID = $badPwId
}

 

#Correlate the logon types
$LogonType = @{
'2' = 'Interactive'
'3' = 'Network'
'4' = 'Batch'
'5' = 'Service'
'7' = 'Unlock'
'8' = 'Networkcleartext'
'9' = 'NewCredentials'
'10' = 'RemoteInteractive'
'11' = 'CachedInteractive'
}

 

#Format the properties
ForEach($event in $events){
[pscustomobject]@{
TargetAccount = $event.properties.Value[5]
LogonType = $LogonType["$($event.properties.Value[10])"]
CallingComputer = $event.Properties.Value[13]
IPAddress = $event.Properties.Value[19]
TimeStamp = $event.TimeCreated
}
}

 

#Bring it all together in a function
Function Get-ADUserBadPasswords {
[CmdletBinding(
DefaultParameterSetName = 'All'
)]
Param (
[Parameter(
ValueFromPipeline = $true,
ParameterSetName = 'ByUser'
)]
[Microsoft.ActiveDirectory.Management.ADUser]$Identity
)
Begin {
$badPwId = 4625
$PDCEmulator = (Get-ADDomain).PDCEmulator
$LogonType = @{
'2' = 'Interactive'
'3' = 'Network'
'4' = 'Batch'
'5' = 'Service'
'7' = 'Unlock'
'8' = 'Networkcleartext'
'9' = 'NewCredentials'
'10' = 'RemoteInteractive'
'11' = 'CachedInteractive'
}
}
Process {
If($PSCmdlet.ParameterSetName -eq 'All'){
#Query event log
$events = Get-WinEvent -ComputerName $PDCEmulator -FilterHashtable @{
LogName = 'Security'
ID = $badPwId
}
}ElseIf($PSCmdlet.ParameterSetName -eq 'ByUser'){
$user = Get-ADUser $Identity
#Query event log
$events = Get-WinEvent -ComputerName $PDCEmulator -FilterHashtable @{
LogName = 'Security'
ID = $badPwId
} | Where-Object {$_.Properties[5].Value -eq $user.SamAccountName}
}
ForEach($event in $events){
[pscustomobject]@{
TargetAccount = $event.properties.Value[5]
LogonType = $LogonType["$($event.properties.Value[10])"]
CallingComputer = $event.Properties.Value[13]
IPAddress = $event.Properties.Value[19]
TimeStamp = $event.TimeCreated
}
}
}
End{}
}

 

#Usage
Get-ADUserBadPasswords | Format-Table

Lockout_BadPW.JPG

 

#Single account
Get-ADUser administrator | Get-ADUserBadPasswords | Format-Table

 

I hope that this information is helpful to you and that you have been given a good "little" foundation. This article/information is by no means complete and exhaustive. But I still hope that this information is helpful to you.

 

Thank you for taking the time to read the article.

 

Happy Hunting, Tom Wechsler

 

P.S. All scripts (#PowerShell, Azure CLI, #Terraform, #ARM) that I use can be found on github! https://github.com/tomwechsler

0 Replies