Microsoft 365 Password Spray Attack Detection
Password spraying against Microsoft 365 / Entra ID remains one of the most effective initial access techniques against SMBs. Attackers use lists of valid corporate usernames (harvested from LinkedIn, HaveIBeenPwned, or prior breaches) and try a small number of common passwords (season+year, company name variations, Welcome1!) across all accounts — staying below per-account lockout thresholds. Microsoft documented Midnight Blizzard (Cozy Bear) using this to gain initial access to Microsoft corporate accounts in 2024. Storm-1152 (bulk account creation / credential fraud group) services this on behalf of other threat actors. NCSC UK has repeatedly warned about Iranian and Russian threat actors using password spraying against UK SMBs in critical sectors. The attack targets legacy authentication protocols (IMAP, SMTP, MAPI) and BasicAuth endpoints that bypass MFA — even if the organisation has MFA deployed for interactive sign-ins.
What is THREAT-M365-PasswordSpray Microsoft 365 Password Spray Attack Detection?
Microsoft 365 Password Spray Attack Detection (THREAT-M365-PasswordSpray) maps to the Credential Access tactic — the adversary is trying to steal account names and passwords in MITRE ATT&CK.
This page provides production-ready detection logic for Microsoft 365 Password Spray Attack Detection, covering the data sources and telemetry it touches: Azure AD Sign-In Logs (AADSignInLogs), Microsoft 365 Defender Sign-In Activity, Azure Sentinel UEBA. The queries below are rated high severity at high confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
- Tactic
- Credential Access
// THREAT: Microsoft 365 Password Spray Detection
// Detects systematic password spraying against M365 / Entra ID
// using sign-in failure patterns across multiple users from shared infrastructure
// Alert 1: Single IP targeting many accounts (spray pattern)
let SprayThreshold_UserCount = 10;
let SprayThreshold_FailureCount = 20;
let SprayWindow = 30min;
AADSignInLogs
| where TimeGenerated > ago(24h)
| where Status.errorCode in (50126, 50053, 50055, 50056, 50064, 50074, 50076, 50079)
// 50126=invalid credentials, 50053=locked, 50055=expired password, 50056=no password
// 50064=credential validation failure, 50074=strong auth required
| summarize
FailureCount=count(),
UniqueUsers=dcount(UserPrincipalName),
TargetUsers=make_set(UserPrincipalName),
ErrorCodes=make_set(Status.errorCode),
Apps=make_set(AppDisplayName)
by IPAddress, bin(TimeGenerated, SprayWindow)
| where UniqueUsers >= SprayThreshold_UserCount and FailureCount >= SprayThreshold_FailureCount
| extend ThreatType = "PasswordSpray_MultipleUsers_SingleIP"
| extend Severity = "High";
// Alert 2: Successful sign-in following spray activity from same IP
let SprayIPs = AADSignInLogs
| where TimeGenerated > ago(24h)
| where Status.errorCode in (50126, 50053, 50055)
| summarize Failures=count(), Users=dcount(UserPrincipalName)
by IPAddress, bin(TimeGenerated, 30m)
| where Failures >= 20 and Users >= 10
| distinct IPAddress;
AADSignInLogs
| where TimeGenerated > ago(24h)
| where Status.errorCode == 0
| where IPAddress in (SprayIPs)
| project TimeGenerated, UserPrincipalName, IPAddress, Location,
AppDisplayName, AuthenticationRequirement, Status
| extend ThreatType = "PasswordSpray_SuccessfulBreachAfterSpray"
| extend Severity = "Critical" Two-stage password spray detection: (1) IP targeting 10+ unique accounts with 20+ authentication failures in a 30-minute window — the spray pattern designed to avoid per-account lockout; (2) successful authentication from an IP that was previously seen spraying — high confidence account takeover indicator. The second alert is the highest priority and warrants immediate response.
Data Sources
Required Tables
False Positives
- Misconfigured applications using a service account that have incorrect credentials and fail authentication across multiple tenants
- Corporate password rotation events where many users have passwords expired simultaneously and attempt sign-in with old credentials
- Load-balanced authentication infrastructure where many employees share an outbound NAT IP (adjust thresholds upward for organisations with NAT)
- Vulnerability scanner credentials testing from a shared assessment IP during authorised penetration tests
Sigma rule & cross-platform mapping
The detection logic for Microsoft 365 Password Spray Attack Detection (THREAT-M365-PasswordSpray) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
product: azure Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for THREAT-M365-PasswordSpray
Testing Methodology
Validate this detection against 1 adversary technique 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 1M365 Password Spray via AADInternals (Low-Slow)
Expected signal: Azure AD Sign-in logs record authentication failures (error code 50126) for each user/password combination tested. Multiple users from single IP within short window.
Response Playbook
Triage
- Identify the source IP(s) and determine their reputation (VirusTotal, AbuseIPDB, Shodan). Common spray sources: Tor exit nodes, VPS hosting (DigitalOcean, Vultr, AWS), residential proxies, or compromised host infrastructure.
- Check for any successful sign-ins from the spray IP. A successful sign-in following spray activity is the highest priority finding — immediately revoke that user's sessions and investigate.
- Determine which user accounts were targeted. Were they high-privilege (Global Admin, Exchange Admin, Finance) or standard users? High-privilege targeting increases impact.
- Identify the authentication protocol used: legacy protocols (IMAP, SMTP, MAPI) allow password spray to bypass MFA. Check AppDisplayName in sign-in logs — 'IMAP', 'Exchange ActiveSync', 'Authenticated SMTP' indicate legacy auth spray.
- Review the timing: is this spray campaign ongoing or completed? If ongoing, implement emergency IP block in Conditional Access Named Locations.
Containment
- Block the spray source IP(s) immediately in Entra ID Conditional Access Named Locations or at the network perimeter.
- For any accounts that had successful sign-in from spray IP: revoke sessions, reset password, force MFA re-registration.
- Disable legacy authentication protocols in Entra ID if not already done: Azure AD > Properties > Manage Security Defaults, or via Conditional Access policy blocking legacy auth for all users.
- Enable Entra ID Smart Lockout if not configured (threshold: 5-10 failed attempts, lockout duration: 30 seconds initially scaling to longer). Default threshold may be too high for spray detection.
- Consider implementing a Conditional Access policy requiring MFA for all sign-ins from outside Named Locations — eliminates password spray value entirely for MFA-registered accounts.
Evidence Collection
- Azure AD Sign-in logs for the spray IP covering full campaign duration
- Entra ID Identity Protection risky sign-in and risky user events for targeted accounts
- Network logs from perimeter firewall or Azure AD Application Proxy showing connection metadata for spray traffic
- Threat intelligence lookups for spray source IPs
Escalation Criteria
- ! Successful authentication by any account following spray activity from same IP
- ! Spray targeting Global Admins, Exchange Admins, Security Admins, or Finance accounts
- ! Legacy authentication (IMAP/SMTP/EAS) spray — these protocols bypass MFA entirely
- ! Multi-source spray where multiple IPs coordinate to spray the same accounts (distributed spray)
- ! Spray sustained for more than 24 hours indicating a motivated, targeted campaign rather than opportunistic scanning
Investigation Guide
Forensic Artifacts
- >
Azure AD Sign-in logs: complete failure and success log for spray IP and timewindow - >
Entra ID Identity Protection: 'Spray' risk detection if licensed - >
Network flow logs to Azure AD authentication endpoints (login.microsoftonline.com) - >
M365 Message Trace: did the spray source IP also send email to the tenant (combined phish+spray)? - >
Threat intelligence: IP history in AbuseIPDB, Shodan banners, WHOIS
Tuning Guidance
The 10 users / 20 failures in 30 minutes threshold is calibrated for SMB environments. For larger organisations with NAT, increase the UniqueUsers threshold to 25-50 (many users sharing a NAT IP will trigger false positives). For environments with legacy auth disabled, you can reduce the threshold to 5 users / 10 failures as spray volume decreases significantly without legacy auth targets. The most important tuning is excluding known outbound NAT IP ranges and authorised scan IPs (pen test firms, vulnerability scanners) from the detection scope.
Hunting Queries
Broad hunt for password spray patterns over 7 days using hourly windows — catches low-slow spray that may fall below the 30-minute threshold in the main detection rule.
AADSignInLogs
| where TimeGenerated > ago(7d)
| where Status.errorCode != 0
// Exclude expected failures: MFA interrupt, redirect to SSO
| where Status.errorCode !in (50074, 50076, 50079, 65001)
| summarize
FailureCount=count(),
UniqueUsers=dcount(UserPrincipalName),
UniqueApps=dcount(AppDisplayName),
Apps=make_set(AppDisplayName)
by IPAddress, bin(TimeGenerated, 1h)
| where UniqueUsers >= 5
| extend SprayScore = UniqueUsers * 1.0 / FailureCount // Low ratio = spray pattern
| sort by UniqueUsers desc index=azure sourcetype="azure:aad:signin" NOT properties.status.error_code IN (0, 50074, 50076)
| bin _time span=1h
| stats count AS Fails, dc(properties.user_principal_name) AS Users
BY properties.ip_address
| where Users >= 5
| eval spray_ratio=Users/Fails
| sort - Users Atomic Red Team Tests
Uses the AADInternals PowerShell module to perform a low-slow password spray against a Microsoft 365 tenant, testing 5 common passwords across a list of 20 accounts. This simulates the spray pattern used by nation-state actors targeting SMBs.
Command
Import-Module AADInternals; $users = Get-Content .\userlist.txt; $passwords = @('Spring2026!', 'Welcome1!', 'P@ssword1', 'Company123!', 'Summer2025'); foreach($pass in $passwords){ foreach($user in $users){ Invoke-AADIntPasswordSpray -Credential (New-Object System.Management.Automation.PSCredential($user, (ConvertTo-SecureString $pass -AsPlainText -Force))) -Tenant '<TENANT_ID>' } } Expected Telemetry
Azure AD Sign-in logs record authentication failures (error code 50126) for each user/password combination tested. Multiple users from single IP within short window.
Expected Detection
Alert fires when UniqueUsers >= 10 and FailureCount >= 20 within 30 minutes from the spray source IP.