Event ID 4625: Failed Login Attempts
Complete troubleshooting guide for Exchange Server Event ID 4625 failed login attempts including security analysis, attack detection, and account lockout resolution.
Table of Contents
Error Overview
Event ID 4625: An Account Failed to Log On
"An account failed to log on. Subject: Security ID: NULL SID. Account Name: -. Logon Type: 3 (Network). Account For Which Logon Failed: Security ID: NULL SID. Account Name: john.doe. Failure Information: Failure Reason: Unknown user name or bad password. Status: 0xC000006D. Sub Status: 0xC0000064."
What This Error Means
Event ID 4625 is logged by Windows Security when an authentication attempt fails. In Exchange environments, this commonly indicates failed OWA logins, Outlook authentication issues, service account problems, or potential security attacks. Understanding and monitoring these events is critical for both troubleshooting and security.
Common Logon Types
- • Type 2: Interactive (local)
- • Type 3: Network (OWA/EWS/RPC)
- • Type 8: NetworkClearText
- • Type 10: RemoteInteractive (RDP)
- • Type 11: CachedInteractive
Key Status Codes
- • 0xC000006D: Bad username/password
- • 0xC0000234: Account locked out
- • 0xC0000072: Account disabled
- • 0xC0000071: Password expired
- • 0xC000006E: Account restriction
Security Alert
High volumes of Event ID 4625 may indicate brute force attacks, credential stuffing, or password spraying. Investigate unusual patterns immediately, especially against administrator accounts or coming from external IP addresses.
Symptoms & Detection
User-Reported Symptoms
- ✗"My account is locked out"
- ✗"OWA says wrong password but it's correct"
- ✗"Outlook keeps asking for credentials"
- ✗"Mobile device can't connect"
- ✗"I changed my password but still can't login"
Administrator Detection
- →Event ID 4625 in Security log
- →Event ID 4740 (account lockout)
- →Unusual login failure patterns
- →SIEM alerts on authentication failures
- →Spike in failed logins from specific IPs
Event Log Entry Example
Log Name: Security
Source: Microsoft-Windows-Security-Auditing
Event ID: 4625
Level: Information
Keywords: Audit Failure
Description: An account failed to log on.
Subject:
Security ID: NULL SID
Account Name: -
Account Domain: -
Logon ID: 0x0
Logon Type: 3
Account For Which Logon Failed:
Security ID: NULL SID
Account Name: john.doe
Account Domain: CONTOSO
Failure Information:
Failure Reason: Unknown user name or bad password.
Status: 0xC000006D
Sub Status: 0xC000006A
Process Information:
Caller Process ID: 0x0
Caller Process Name: -
Network Information:
Workstation Name: CLIENT01
Source Network Address: 10.0.1.50
Source Port: 52431Common Causes
Incorrect Password Entry
Users typing wrong passwords, especially after password changes. Cached credentials in Outlook, mobile devices, or browsers may use old passwords after a change.
Account Lockout
Account has been locked due to too many failed login attempts. This can be caused by user error, cached credentials, or malicious activity.
Brute Force/Password Spray Attack
Attackers attempting to guess passwords through automated tools. Password spraying uses common passwords against many accounts to avoid lockouts.
Service Account Issues
Service accounts with changed passwords still configured in scheduled tasks, application pools, or Exchange services. These generate continuous login failures.
Mobile Device with Old Password
After password changes, mobile devices configured with ActiveSync continue trying to authenticate with the old password, causing repeated failures.
Diagnostic Steps
Step 1: Query Failed Login Events
# Get recent failed login attempts
$startTime = (Get-Date).AddHours(-24)
$failedLogins = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = $startTime
} -ErrorAction SilentlyContinue
Write-Host "=== Failed Login Summary (Last 24 Hours) ===" -ForegroundColor Cyan
Write-Host "Total failed logins: $($failedLogins.Count)"
# Group by target account
Write-Host "`n=== Failed Logins by Account ===" -ForegroundColor Yellow
$failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
$targetUser = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'}).'#text'
$targetUser
} | Group-Object | Sort-Object Count -Descending | Select-Object Name, Count -First 15 |
Format-Table -AutoSize
# Group by source IP
Write-Host "`n=== Failed Logins by Source IP ===" -ForegroundColor Yellow
$failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
$sourceIP = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'}).'#text'
$sourceIP
} | Group-Object | Sort-Object Count -Descending | Select-Object Name, Count -First 15 |
Format-Table -AutoSize
# Group by failure reason
Write-Host "`n=== Failed Logins by Failure Status ===" -ForegroundColor Yellow
$failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
$status = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'Status'}).'#text'
$status
} | Group-Object | Sort-Object Count -Descending | Format-Table -AutoSizeStep 2: Check Specific Account Details
# Get detailed failure information for specific account
$targetAccount = "john.doe"
$accountFailures = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = (Get-Date).AddHours(-24)
} -ErrorAction SilentlyContinue | Where-Object {
$xml = [xml]$_.ToXml()
$user = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'}).'#text'
$user -eq $targetAccount
}
Write-Host "=== Failed Logins for $targetAccount ===" -ForegroundColor Cyan
$accountFailures | ForEach-Object {
$xml = [xml]$_.ToXml()
[PSCustomObject]@{
Time = $_.TimeCreated
SourceIP = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'}).'#text'
Workstation = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'WorkstationName'}).'#text'
Status = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'Status'}).'#text'
SubStatus = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'SubStatus'}).'#text'
LogonType = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'LogonType'}).'#text'
}
} | Format-Table -AutoSize
# Check if account is locked
Get-ADUser -Identity $targetAccount -Properties LockedOut, LockoutTime, BadLogonCount |
Select-Object Name, LockedOut, LockoutTime, BadLogonCount | Format-ListStep 3: Identify Attack Patterns
# Analyze for potential attack patterns
$failedLogins = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = (Get-Date).AddHours(-4)
} -ErrorAction SilentlyContinue
# Check for high-volume from single IP (possible brute force)
Write-Host "=== Potential Brute Force Sources ===" -ForegroundColor Red
$ipGroups = $failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'}).'#text'
} | Group-Object
$ipGroups | Where-Object {$_.Count -gt 20} | Sort-Object Count -Descending |
Select-Object @{N='SourceIP';E={$_.Name}}, Count | Format-Table -AutoSize
# Check for password spray (same password against multiple accounts)
# Look for IPs hitting many different accounts
Write-Host "`n=== Potential Password Spray Sources ===" -ForegroundColor Red
$failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
[PSCustomObject]@{
IP = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'}).'#text'
User = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'}).'#text'
}
} | Group-Object IP | Where-Object {
($_.Group | Select-Object -ExpandProperty User -Unique).Count -gt 5
} | Select-Object @{N='SourceIP';E={$_.Name}},
@{N='UniqueAccounts';E={($_.Group | Select-Object User -Unique).Count}},
@{N='TotalAttempts';E={$_.Count}} |
Sort-Object TotalAttempts -Descending | Format-Table -AutoSize
# Check for off-hours activity
Write-Host "`n=== Off-Hours Failed Logins (10PM-6AM) ==="-6AM) ===" -ForegroundColor Yellow
$failedLogins | Where-Object {
$_.TimeCreated.Hour -ge 22 -or $_.TimeCreated.Hour -lt 6
} | Measure-Object | Select-Object -ExpandProperty CountStep 4: Check Account Lockout Source
# Find the source of account lockouts
$lockedAccount = "john.doe"
# Check Event ID 4740 (account lockout) on domain controller
$lockoutEvents = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4740
StartTime = (Get-Date).AddDays(-7)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match $lockedAccount
}
Write-Host "=== Account Lockout Events for $lockedAccount ===" -ForegroundColor Cyan
$lockoutEvents | ForEach-Object {
$xml = [xml]$_.ToXml()
[PSCustomObject]@{
Time = $_.TimeCreated
Account = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'}).'#text'
CallerComputer = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetDomainName'}).'#text'
DC = $_.MachineName
}
} | Format-Table -AutoSize
# Find the PDC emulator (where lockout events are logged)
$pdc = (Get-ADDomain).PDCEmulator
Write-Host "`nPDC Emulator: $pdc"
Write-Host "Query Event ID 4740 on this server for authoritative lockout source"
# Alternative: Use LockoutStatus.exe from Microsoft (download separately)
# Or use AD account lockout tools
# Get all bad password attempts for the account
Get-ADUser -Identity $lockedAccount -Properties badPwdCount, LastBadPasswordAttempt |
Select-Object Name, badPwdCount, LastBadPasswordAttempt | Format-ListPro Tip
Microsoft provides the Account Lockout and Management Tools (ALTools.exe) which includes LockoutStatus.exe - a GUI tool that queries all domain controllers to find the exact source of account lockouts. This is invaluable for tracking down lockout sources.
Quick Fix
Immediate Actions for Account Issues
Use these steps to quickly resolve user access issues:
# Step 1: Unlock the account
$targetAccount = "john.doe"
Unlock-ADAccount -Identity $targetAccount
Write-Host "Account $targetAccount unlocked" -ForegroundColor Green
# Step 2: Check account status
Get-ADUser -Identity $targetAccount -Properties * |
Select-Object Name, Enabled, LockedOut, PasswordExpired,
PasswordLastSet, AccountExpirationDate, LastLogonDate | Format-List
# Step 3: If password issues, reset password
# $newPassword = Read-Host "Enter new password" -AsSecureString-Host "Enter new password" -AsSecureString
# Set-ADAccountPassword -Identity $targetAccount -NewPassword $newPassword -Reset-Identity $targetAccount -NewPassword $newPassword -Reset
# Set-ADUser -Identity $targetAccount -ChangePasswordAtLogon $true-Identity $targetAccount -ChangePasswordAtLogon $true
# Step 4: Check for cached credentials causing lockouts
Write-Host "`n=== Common Cached Credential Locations ===" -ForegroundColor Yellow
Write-Host "1. Windows Credential Manager (control keymgr.dll)"
Write-Host "2. Outlook profile (remove and re-add)"-add)"
Write-Host "3. Mobile devices (update or remove/re-add account)"-add account)"
Write-Host "4. Mapped drives with saved credentials"
Write-Host "5. Scheduled tasks running as user"
Write-Host "6. Services running as user"
Write-Host "7. Browser saved passwords for OWA"
# Step 5: Force AD replication to clear lockout quickly
# repadmin /syncall /APeD
# Step 6: If attack suspected - block source IP
# (Do this at firewall level)
# Get-NetFirewallRule | Where-Object {$_.DisplayName -match "Exchange"}-Object {$_.DisplayName -match "Exchange"}
# For immediate block, use Windows Firewall:
# New-NetFirewallRule -DisplayName "Block Attacker IP" -Direction Inbound -RemoteAddress "attacker.ip.address" -Action Block-DisplayName "Block Attacker IP" -Direction Inbound -RemoteAddress "attacker.ip.address" -Action BlockNote: After unlocking an account, work with the user to identify and update any devices or applications with cached credentials to prevent immediate re-lockout.
Detailed Solutions
Solution 1: Configure Account Lockout Policies
Implement balanced lockout policies to protect accounts without impacting users:
# View current domain lockout policy
Get-ADDefaultDomainPasswordPolicy |
Select-Object LockoutDuration, LockoutObservationWindow, LockoutThreshold |
Format-List
# Recommended settings for most organizations:
# - Lockout threshold: 5-10 attempts-10 attempts
# - Lockout duration: 30 minutes
# - Reset counter after: 30 minutes
# Set via Group Policy (preferred) or PowerShell:
# Set-ADDefaultDomainPasswordPolicy -Identity "contoso.com" -LockoutDuration "00:30:00" -LockoutObservationWindow "00:30:00" -LockoutThreshold 5-Identity "contoso.com" -LockoutDuration "00:30:00"30:00" -LockoutObservationWindow "00:30:00"30:00" -LockoutThreshold 5
# For fine-grained password policies (specific groups)
New-ADFineGrainedPasswordPolicy -Name "ServiceAccountPolicy" -Precedence 10 -LockoutDuration "01:00:00"00:00" -LockoutObservationWindow "01:00:00"00:00" -LockoutThreshold 10 -ComplexityEnabled $true -MinPasswordLength 16
# Apply to service accounts group
Add-ADFineGrainedPasswordPolicySubject -Identity "ServiceAccountPolicy" -Subjects "ServiceAccounts"
# Verify policy assignment
Get-ADUserResultantPasswordPolicy -Identity "svc_exchange"Solution 2: Implement IP-Based Rate Limiting
Add additional protection against brute force attacks at the network level:
# Option 1: Use IIS Dynamic IP Restrictions (built-in)-in)
Import-Module WebAdministration
# Enable dynamic IP restrictions for OWA
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST/Default Web Site/owa' -Filter "system.webServer/security/dynamicIpSecurity/denyByConcurrentRequests" -Name "enabled" -Value "True"
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST/Default Web Site/owa' -Filter "system.webServer/security/dynamicIpSecurity/denyByConcurrentRequests" -Name "maxConcurrentRequests" -Value "20"
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST/Default Web Site/owa' -Filter "system.webServer/security/dynamicIpSecurity/denyByRequestRate" -Name "enabled" -Value "True"
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST/Default Web Site/owa' -Filter "system.webServer/security/dynamicIpSecurity/denyByRequestRate" -Name "maxRequests" -Value "30"
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST/Default Web Site/owa' -Filter "system.webServer/security/dynamicIpSecurity/denyByRequestRate" -Name "requestIntervalInMilliseconds" -Value "2000"
# Option 2: Use Windows Firewall to block known bad IPs
$badIPs = @("1.2.3.4"2.3.4", "5.6.7.8"6.7.8") # Add confirmed attacker IPs
foreach ($ip in $badIPs) {
New-NetFirewallRule -DisplayName "Block Attacker $ip" -Direction Inbound -RemoteAddress $ip -Action Block -Profile Any
}
# Option 3: Configure fail2ban-like solution with scheduled task-like solution with scheduled task
# (Script to monitor events and block IPs dynamically)Solution 3: Implement Multi-Factor Authentication
MFA significantly reduces the risk from password attacks:
# For Azure AD / Microsoft 365 hybrid environments:
# Enable MFA through Azure AD
# Check current authentication methods
Get-ADUser -Identity "john.doe" -Properties * |
Select-Object Name, msDS-StrongAuthenticationDetail
# For on-premises, consider:
# 1. Azure MFA Server (being deprecated)
# 2. Azure AD Application Proxy with Azure MFA
# 3. Third-party MFA solutions (Duo, RSA, etc.)-party MFA solutions (Duo, RSA, etc.)
# 4. Certificate-based authentication-based authentication
# Enable pre-authentication with AD FS + MFA
# This requires AD FS configuration
# For OWA-specific MFA, configure claims-based authentication-based authentication
# Set-OrganizationConfig -OAuth2ClientProfileEnabled $true-OAuth2ClientProfileEnabled $true
# Block legacy authentication (doesn't support MFA)
# In Exchange:
Set-OrganizationConfig -DefaultAuthenticationPolicy "Block-Legacy-Auth"-Auth"
# Create authentication policy to block legacy protocols
New-AuthenticationPolicy -Name "Block-Legacy-Auth"-Auth" -AllowBasicAuthActiveSync $false -AllowBasicAuthAutodiscover $false -AllowBasicAuthImap $false -AllowBasicAuthMapi $false -AllowBasicAuthOfflineAddressBook $false -AllowBasicAuthOutlookService $false -AllowBasicAuthPop $false -AllowBasicAuthPowershell $false -AllowBasicAuthReportingWebServices $false -AllowBasicAuthRpc $false -AllowBasicAuthSmtp $false -AllowBasicAuthWebServices $false
Write-Host "MFA and modern authentication recommended for all external access" -ForegroundColor YellowSolution 4: Set Up Security Monitoring and Alerting
Implement automated monitoring for authentication failures:
# Create scheduled task to monitor and alert on high failure rates
# Detection script (save as Check-FailedLogins.ps1)
$threshold = 50 # Alert if more than 50 failures per hour
$checkPeriod = 1 # Hours
$failures = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = (Get-Date).AddHours(-$checkPeriod)
} -ErrorAction SilentlyContinue
if ($failures.Count -gt $threshold) {
# Get details for alert
$topAccounts = $failures | ForEach-Object {
$xml = [xml]$_.ToXml()
($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'}).'#text'
} | Group-Object | Sort-Object Count -Descending | Select-Object -First 5
$topIPs = $failures | ForEach-Object {
$xml = [xml]$_.ToXml()
($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'}).'#text'
} | Group-Object | Sort-Object Count -Descending | Select-Object -First 5
$alertBody = @"
SECURITY ALERT: High Volume of Failed Logins
Total failures in last $checkPeriod hour(s): $($failures.Count)
Top targeted accounts:
$($topAccounts | ForEach-Object { "$failures.Count)
Top targeted accounts:
$($topAccounts | ForEach-Object { "$($_.Name): $($_.Count)" } | Out-String)
Top source IPs:
$($topIPs | ForEach-Object { "$topIPs | ForEach-Object { "$($_.Name): $($_.Count)" } | Out-String)
Please investigate immediately!
"@
# Send alert
Send-MailMessage -To "security@domain.com" -From "monitoring@domain.com" -Subject "ALERT: High Failed Login Volume on $env:COMPUTERNAME" -Body $alertBody -SmtpServer "smtp.domain.com" -Priority High
# Log to event log
Write-EventLog -LogName Application -Source "SecurityMonitoring" -EventId 9000 -EntryType Warning -Message $alertBody
}
# Register the script as scheduled task
# Run every 15 minutesDanger Zone
Be careful when blocking IPs - ensure they are truly malicious and not legitimate users behind NAT, VPN gateways, or corporate proxies. A single blocked IP could affect many users. Always verify before blocking and maintain a process to review and remove blocks.
Verification Steps
Verify Security Issue Resolution
# Verify authentication is working for affected users
$testUser = "john.doe"
# Check account status
$account = Get-ADUser -Identity $testUser -Properties *
Write-Host "=== Account Status ===" -ForegroundColor Cyan
Write-Host "Enabled: $($account.Enabled)"
Write-Host "Locked Out: $($account.LockedOut)"
Write-Host "Password Expired: $($account.PasswordExpired)"
Write-Host "Bad Password Count: $($account.badPwdCount)"
Write-Host "Last Logon: $($account.LastLogonDate)"
# Check for recent successful logins (Event ID 4624)
$successLogins = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4624
StartTime = (Get-Date).AddHours(-1)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match $testUser
}
Write-Host "`nRecent successful logins: $($successLogins.Count)"
# Check that failure rate has decreased
$recentFailures = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = (Get-Date).AddHours(-1)
} -ErrorAction SilentlyContinue
Write-Host "Failed logins (last hour): $($recentFailures.Count)"
# Compare to previous period
$previousFailures = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = (Get-Date).AddHours(-25)
EndTime = (Get-Date).AddHours(-24)
} -ErrorAction SilentlyContinue
Write-Host "Failed logins (24h ago same hour): $($previousFailures.Count)"
# Success indicators
if ($recentFailures.Count -lt $previousFailures.Count) {
Write-Host "`nStatus: IMPROVED - Failure rate decreased" -ForegroundColor Green
} else {
Write-Host "`nStatus: INVESTIGATE - Failures still elevated" -ForegroundColor Yellow
}✓ Success Indicators
- • Affected user can login
- • Failure rate normalized
- • No new lockout events
- • Attack source blocked
⚠ Warning Signs
- • Failures from new IPs
- • User locked out again
- • Different accounts targeted
- • Pattern continues off-hours
✗ Failure Indicators
- • Attack continues
- • Multiple accounts affected
- • Successful breach suspected
- • Source evading blocks
Prevention Strategies
Authentication Security Best Practices
- ✓Implement MFA
Required for all external access
- ✓Block legacy authentication
Disable protocols that can't use MFA
- ✓Monitor continuously
Alert on unusual failure patterns
- ✓Use strong passwords
Enforce complexity and check against breach lists
Real-Time Monitoring Script
# Real-time failed login monitor
$threshold = 10 # Alert per account per hour
while ($true) {
$failures = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = (Get-Date).AddHours(-1)
} -MaxEvents 1000 -ErrorAction SilentlyContinue
$byAccount = $failures | ForEach-Object {
$xml = [xml]$_.ToXml()
($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'}).'#text'
} | Group-Object | Where-Object {$_.Count -gt $threshold}
if ($byAccount) {
Write-Host "$(Get-Date) - HIGH FAILURES DETECTED:" -ForegroundColor Red
$byAccount | ForEach-Object {
Write-Host " $($_.Name): $($_.Count) failures"$_.Count) failures"
}
}
Start-Sleep -Seconds 300 # Check every 5 minutes
}When to Escalate
Escalate to Security Team When:
- →Evidence of successful unauthorized access
- →Coordinated attack against multiple accounts
- →Attack originating from internal network
- →Attacks targeting administrator accounts
- →Attack pattern indicates advanced threat actor
Need Expert Exchange Security Help?
Our Exchange Server security specialists can help investigate authentication attacks, implement security controls, and harden your environment against threats.
15 Minutes average response time for security incidents
Frequently Asked Questions
Can't Resolve LOGON_FAILURE?
Exchange errors can cause data loss or extended downtime. Our specialists are available 24/7 to help.
Emergency help - Chat with usMedha Cloud Exchange Server Team
Microsoft Exchange Specialists
Our Exchange Server specialists have 15+ years of combined experience managing enterprise email environments. We provide 24/7 support, emergency troubleshooting, and ongoing administration for businesses worldwide.