Detect Password Spraying in Microsoft Sentinel
Adversaries may use a single or small list of commonly used passwords against many different accounts to attempt to acquire valid account credentials. Password spraying uses one password (e.g. 'Password01'), or a small list of commonly used passwords, that may match the complexity policy of the domain. Logins are attempted with that password against many different accounts on a network to avoid account lockouts that would normally occur when brute forcing a single account with many passwords. This technique is deliberately throttled to avoid triggering per-account lockout thresholds — the defining characteristic that distinguishes spraying from brute force (T1110.001). Adversaries including APT28, APT29, HAFNIUM, Storm-0940, Chimera, and APT33 have used this technique at scale against OWA, Microsoft 365, VPN portals, SSH, RDP, SMB, and LDAP. Slow-spray variants (approximately 4 attempts per account per hour) are specifically designed to evade detection thresholds, and Kerberos-based spraying is used to avoid generating the high-visibility Event ID 4625 typically alerted on.
MITRE ATT&CK
- Tactic
- Credential Access
- Technique
- T1110 Brute Force
- Sub-technique
- T1110.003 Password Spraying
- Canonical reference
- https://attack.mitre.org/techniques/T1110/003/
KQL Detection Query
let SprayAccountThreshold = 10;
let BruteForceMaxPerAccount = 5;
let LookbackWindow = 24h;
let SprayWindow = 30m;
// Azure AD / Entra ID password spray detection via SigninLogs
let AzureADSpray = SigninLogs
| where TimeGenerated > ago(LookbackWindow)
| where ResultType != "0"
| where ResultType in (
"50126", // Invalid username or password
"50055", // Expired password
"50056", // Null password in store
"50064", // Credentials expired
"50053", // Account locked out
"50057", // User account disabled
"50074", // Strong auth required but not provided
"50059", // Tenant not found
"50076" // MFA required but not provided
)
| where isnotempty(IPAddress) and IPAddress !in ("127.0.0.1", "::1")
| summarize
FailureCount = count(),
DistinctAccounts = dcount(UserPrincipalName),
TargetAccounts = make_set(UserPrincipalName, 30),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
TargetApps = make_set(AppDisplayName, 10),
UserAgents = make_set(UserAgent, 5)
by IPAddress, bin(TimeGenerated, SprayWindow)
| where DistinctAccounts >= SprayAccountThreshold
| extend AvgFailuresPerAccount = round(toreal(FailureCount) / toreal(DistinctAccounts), 2)
| where AvgFailuresPerAccount <= BruteForceMaxPerAccount
| extend DetectionSource = "AzureAD_SigninLogs";
// On-premises AD password spray detection via Security Event 4625
let OnPremADSpray = SecurityEvent
| where TimeGenerated > ago(LookbackWindow)
| where EventID == 4625
| where LogonType in (3, 10) // Network, RemoteInteractive
| where IpAddress !in ("-", "", "::1", "127.0.0.1")
| summarize
FailureCount = count(),
DistinctAccounts = dcount(TargetUserName),
TargetAccounts = make_set(TargetUserName, 30),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
TargetApps = make_set(WorkstationName, 10),
UserAgents = dynamic([])
by IpAddress, bin(TimeGenerated, SprayWindow)
| where DistinctAccounts >= SprayAccountThreshold
| extend AvgFailuresPerAccount = round(toreal(FailureCount) / toreal(DistinctAccounts), 2)
| where AvgFailuresPerAccount <= BruteForceMaxPerAccount
| extend DetectionSource = "OnPremAD_SecurityEvent"
| project-rename IPAddress = IpAddress;
union AzureADSpray, OnPremADSpray
| sort by DistinctAccounts desc Detects password spraying by identifying a high number of distinct failed authentication targets originating from a single source IP within a 30-minute window. Covers both Azure AD/Entra ID (SigninLogs with error codes for invalid credentials, locked and disabled accounts) and on-premises Active Directory (SecurityEvent 4625, logon types 3 and 10). The key heuristic distinguishing spraying from brute force is AvgFailuresPerAccount: spraying hits many accounts with few attempts each (<=5 average) to avoid lockout thresholds, while brute force concentrates attempts against fewer accounts. Results are unioned from both data sources into a single alert stream.
Data Sources
Required Tables
False Positives & Tuning
- Misconfigured service accounts using stale credentials that authenticate against multiple systems simultaneously during a configuration failure event
- Authorized penetration testing or red team exercises targeting multiple accounts with common passwords from a designated test IP
- ADFS or federated identity proxy services whose single IP proxies authentication for all federated users — these IPs will appear as the source for all federation failures
- Vulnerability scanners (Tenable, Qualys, Rapid7) performing authenticated scans with cycling credentials across multiple hosts
- Password synchronization failures during Active Directory migrations or forest trust establishment generating bulk 4625 events from the migration service account IP
Other platforms for T1110.003
Testing Methodology
Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Local Account Password Spray via Net Use (Windows)
Expected signal: Windows Security Event ID 4625 — one per iteration with LogonType=3, TargetUserName=each account in the list, SubStatus=0xC0000064 (unknown user) for non-existent accounts or 0xC000006A (wrong password) for existing accounts, IpAddress=127.0.0.1. SecurityEvent 4648 (Explicit Credential Logon) may also fire. With 12 accounts in the list, DistinctAccounts=12 will exceed the default threshold of 10.
- Test 2Azure AD Password Spray via PowerShell OAuth Token Request
Expected signal: Azure AD SigninLogs entries (visible in Azure Portal > Azure AD > Sign-in logs within 5-15 minutes) for each request: ResultType=50126 (InvalidUserNameOrPassword) or 50057 (account disabled), IPAddress=your public egress IP, AppDisplayName='Microsoft Azure PowerShell' or 'Azure Active Directory PowerShell', UserAgent containing 'PowerShell'. With 11 accounts in the list, DistinctAccounts=11 exceeds the SprayAccountThreshold=10.
- Test 3Kerberos Password Spray via Rubeus (Low-Visibility Technique)
Expected signal: Windows Security Event ID 4771 on the Domain Controller for each domain account targeted. Fields: Client Address = spray source IP, Account Name = target username, Service Name = krbtgt, Failure Code = 0x18 (KRB_AP_ERR_BAD_INTEGRITY — wrong password) or 0x6 (KDC_ERR_C_PRINCIPAL_UNKNOWN — unknown account). Event ID 4768 (TGT Request) may appear for accounts that receive AS-REQ. The /delay 2000ms and /jitter 30% simulate slow-spray behavior. Main 4625-based detection does NOT fire, demonstrating the detection gap this technique exploits.
- Test 4SMB Password Spray via CrackMapExec Against Subnet
Expected signal: Windows Security Event ID 4625 on the target host for each account attempted: LogonType=3 (Network), SubStatus=0xC000006A (wrong password) for valid accounts or 0xC0000064 (unknown user) for invalid accounts, IpAddress=attacker IP, WorkstationName=blank (common CrackMapExec behavior), AuthenticationPackageName=NTLM. With 10 accounts, DistinctAccounts=10 meets the default detection threshold. EventID 7045 may appear if CrackMapExec uses service-based execution. Sysmon Event ID 3 (Network Connection) visible on attacking host.
References (13)
- https://attack.mitre.org/techniques/T1110/003/
- https://www.trimarcsecurity.com/single-post/2018/05/06/Trimarc-Research-Detecting-Password-Spraying-with-Security-Event-Auditing
- https://www.microsoft.com/en-us/security/blog/2024/10/31/chinese-threat-actor-storm-0940-uses-credentials-from-password-spray-attacks-from-a-covert-network/
- http://www.blackhillsinfosec.com/?p=4645
- https://www.us-cert.gov/ncas/alerts/TA18-086A
- https://github.com/dafthack/MSOLSpray
- https://github.com/dafthack/MailSniper
- https://github.com/ropnop/kerbrute
- https://github.com/GhostPack/Rubeus
- https://learn.microsoft.com/en-us/azure/active-directory/reports-monitoring/concept-sign-ins
- https://learn.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes
- https://www.microsoft.com/en-us/security/blog/2021/06/25/nobelium-attacking-more-than-150-organizations/
- https://github.com/byt3bl33d3r/CrackMapExec
Unlock Pro Content
Get the full detection package for T1110.003 including response playbook, investigation guide, and atomic red team tests.