Detect Password Spraying in CrowdStrike LogScale
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/
LogScale Detection Query
// CrowdStrike LogScale: Password Spray Detection
// Source: Falcon endpoint UserLogonFailed2 events (network and remote interactive logon types)
#event_simpleName = UserLogonFailed2
| LogonType in ["3", "10"]
| IpAddress != "127.0.0.1"
| IpAddress != "::1"
| IpAddress != "-"
| IpAddress != ""
| UserName != ""
| UserName != "ANONYMOUS LOGON"
| bucket(span=30m)
| groupBy(
[IpAddress, _bucket],
function=[
count(as=FailureCount),
count(field=UserName, distinct=true, as=DistinctAccounts),
collect(field=UserName, limit=30, as=TargetAccounts),
collect(field=ComputerName, limit=20, as=TargetHosts),
min(field=@timestamp, as=FirstSeen),
max(field=@timestamp, as=LastSeen)
]
)
| DistinctAccounts >= 10
| AvgFailuresPerAccount := FailureCount / DistinctAccounts
| AvgFailuresPerAccount <= 5
| SprayDurationSeconds := LastSeen - FirstSeen
| sort(DistinctAccounts, order=desc)
| table([
_bucket, IpAddress, FailureCount, DistinctAccounts,
AvgFailuresPerAccount, SprayDurationSeconds,
TargetAccounts, TargetHosts, FirstSeen, LastSeen
]) CrowdStrike LogScale (Humio CQL) query detecting password spraying using Falcon sensor UserLogonFailed2 events. Groups failed logon events into 30-minute buckets via `bucket(span=30m)`, then uses `groupBy` with `count(field=UserName, distinct=true)` to count distinct targeted accounts per source IP per window. Triggers when a single IP targets 10 or more distinct accounts with an average of 5 or fewer failures per account. `AvgFailuresPerAccount <= 5` and `DistinctAccounts >= 10` act as inline pipeline filters in LogScale. Important coverage note: UserLogonFailed2 captures failures on Falcon-instrumented endpoints only. For authentication failures against OWA, VPN portals, ADFS, or other network services not running the Falcon sensor, supplement with a second query against Windows Security Events (EventCode=4625) forwarded to LogScale via the Falcon LogScale Collector, replacing `#event_simpleName=UserLogonFailed2` with `EventCode=4625` and using the `IpAddress` and `TargetUserName` fields from the raw event.
Data Sources
Required Tables
False Positives & Tuning
- Jump servers and privileged access workstations (PAWs) used by multiple administrators — all RDP and network logon failures from many admin accounts appear sourced from the jump host's IP rather than the originating workstation, easily crossing the 10-account threshold during normal helpdesk activity
- VDI infrastructure (Citrix Virtual Apps, VMware Horizon) where many user desktop sessions originate from a small pool of broker or hypervisor management IPs — failed logons from VDI users consolidate under infrastructure addresses in Falcon telemetry
- Endpoint visibility gaps where Falcon is not deployed on domain controllers or authentication servers mean that network-level spray against OWA, ADFS, or RADIUS will not generate UserLogonFailed2 events and will be missed by this query — use the forwarded Windows Security Event variant for complete coverage
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.