Password Spraying Attack
Password spraying is a type of brute force attack that tests a small number of commonly used passwords against a large number of user accounts simultaneously. Unlike traditional brute force that tries many passwords against one account (triggering lockout), password spraying tries one password against all accounts before moving to the next, evading account lockout policies while exploiting the statistical certainty that some users will have weak passwords.
How Password Spraying Attack Works
Password spraying exploits a fundamental tension in security design: account lockout policies protect individual accounts from brute force but cannot protect against attacks distributed across many accounts. If an organization has 10,000 users and the attacker tries just one password (e.g., 'Summer2026!'), statistically 1-3% of users will have that exact password, yielding 100-300 compromised accounts โ all without triggering a single lockout. The attack is particularly effective against enterprise environments using Active Directory, Microsoft 365, and other centralized identity systems.
Enumerate valid usernames
The attacker harvests valid usernames through multiple sources: LinkedIn scraping to derive corporate email formats (firstname.lastname@company.com), email harvesting from public records and data breaches, OSINT tools (theHarvester, Hunter.io), timing-based username enumeration on login pages (different response times for valid vs. invalid usernames), and Azure AD/Microsoft 365 user enumeration via publicly accessible APIs. A single organization can yield thousands of valid usernames.
Craft targeted password list
Instead of generic wordlists, the attacker creates a small, targeted password list based on the organization's context: company name + year (CompanyName2026), seasonal patterns (Winter2026!, Summer2026!), common patterns meeting complexity requirements (Welcome1!, Password123!, Qwerty@123), recently expired passwords with incremented numbers, sports teams or local references, and default passwords for new accounts. A focused list of 5-20 passwords is more effective than thousands of random ones.
Execute distributed low-velocity attack
The attacker tests one password against all enumerated accounts, then waits (often 30-60 minutes or longer) before trying the next password. This delay keeps the attempt count per account well below lockout thresholds (typically 3-5 attempts in 15-30 minutes). Advanced attackers distribute requests across multiple source IPs using cloud infrastructure, residential proxies, or compromised devices to avoid IP-based detection. Each password is tested against the entire user base before advancing.
Identify and exploit successful logins
The attacker monitors for successful authentication responses, which typically differ from failures in HTTP status codes, response headers, or redirect destinations. Each successful login is immediately tested for access scope: email, VPN, cloud services, internal applications. The attacker prioritizes accounts with administrative privileges, access to sensitive data, or positions that enable further social engineering (executives, IT staff, finance).
Establish persistence and escalate
Compromised accounts are used to establish persistence before the password is potentially reset: the attacker enrolls their own MFA device, creates mail forwarding rules, generates application passwords, or provisions OAuth tokens. High-value accounts are used for lateral movement within the organization โ accessing SharePoint documents, internal wikis, and other systems that rely on the same identity provider, progressively expanding access to more sensitive resources.
Real-World Examples
Midnight Blizzard (Nobelium) attack on Microsoft
The Russian state-sponsored group Midnight Blizzard (formerly Nobelium, the SolarWinds attackers) used password spraying to compromise a legacy non-production Microsoft test tenant account that lacked MFA. From this initial foothold, they accessed Microsoft's corporate email systems and read emails of senior leadership and cybersecurity team members for several months. The incident demonstrated that even the world's largest technology companies are vulnerable to password spraying against accounts with inadequate protections.
Iranian APT campaigns against US government
The US Department of Justice indicted Iranian hackers who conducted a massive password spraying campaign targeting over 7,998 accounts across 176 universities in 21 countries, 47 private companies, the United Nations, and US government entities. The attackers successfully compromised approximately 3,768 accounts, stealing over 31 terabytes of data worth approximately $3.4 billion in intellectual property.
Citrix breach via password spraying
Attackers used password spraying to gain initial access to Citrix's internal network, subsequently accessing and downloading business documents for approximately six months before detection. The FBI alerted Citrix to the breach. The attackers, attributed to the IRIDIUM group, accessed approximately 6 TB of sensitive internal data. Citrix later confirmed that the initial access point was a combination of password spraying and credential stuffing against employee accounts.
Impact & Risk Assessment
Password spraying is one of the most effective initial access techniques used by both cybercriminals and nation-state actors. Its effectiveness stems from the mathematical certainty that in any large user population, some users will have weak passwords. A successful spray typically compromises 1-3% of targeted accounts, which in a 10,000-user organization means 100-300 accounts. The impact cascades: compromised email accounts enable business email compromise (BEC) and internal phishing; VPN access provides network-level access to internal resources; cloud admin accounts expose the entire cloud infrastructure. Microsoft's Digital Defense Report 2024 identified password spraying as the most common identity-based attack vector, responsible for over one-third of enterprise account compromises.
How to Detect Password Spraying Attack
Detecting password spraying requires shifting from per-account analysis to cross-account pattern recognition. Monitor for: simultaneous failed login attempts across many accounts with the same password (the defining spray pattern), failed login spikes across the organization even when no individual account exceeds lockout thresholds, authentication attempts from cloud hosting IPs or known proxy services, login attempts outside business hours across many accounts, successful logins immediately following a pattern of distributed failures, and geographic anomalies (login attempts from countries where the organization has no employees). Implement centralized authentication logging that correlates events across all identity providers (AD, Azure AD, Okta, LDAP). Deploy SIEM rules specifically designed to detect low-and-slow password spraying patterns. Monitor for OAuth token generation and MFA enrollment changes following successful authentication from unusual sources.
How to Prevent Password Spraying Attack
Implement multi-factor authentication for all accounts โ MFA neutralizes password spraying even when weak passwords are discovered, because the attacker lacks the second factor. Deploy passwordless authentication (FIDO2, passkeys) where possible to eliminate password-based attacks entirely. Enforce password policies that ban common patterns: check against breach lists (HaveIBeenPwned), block company name variations, seasonal patterns, and keyboard walks. Implement smart lockout that considers distributed patterns (Azure AD Smart Lockout, similar for other providers) rather than only per-account thresholds. Deploy conditional access policies that restrict authentication based on location, device compliance, and risk signals. Use honeypot accounts โ decoy accounts that generate immediate alerts when authentication is attempted. Implement login rate limiting that considers cross-account patterns, not just per-account rates. Regularly audit service accounts, legacy accounts, and non-production accounts, which are frequently overlooked and lack MFA. Disable legacy authentication protocols (IMAP, POP3, SMTP AUTH) that don't support MFA.
Code Examples
from collections import defaultdict
from datetime import datetime, timedelta
def detect_password_spray(auth_logs, window_minutes=30, threshold=10):
"""Detect password spraying by identifying coordinated failed logins
across multiple accounts within a time window."""
# Group failed logins by time window
windows = defaultdict(lambda: {
'source_ips': set(),
'target_accounts': set(),
'failed_count': 0,
'timestamps': []
})
for log in auth_logs:
if log['status'] != 'failed':
continue
# Round timestamp to window
window_key = log['timestamp'].replace(
minute=(log['timestamp'].minute // window_minutes) * window_minutes,
second=0, microsecond=0
)
w = windows[window_key]
w['source_ips'].add(log['source_ip'])
w['target_accounts'].add(log['username'])
w['failed_count'] += 1
w['timestamps'].append(log['timestamp'])
alerts = []
for window_start, data in windows.items():
# Spray signature: many accounts, few IPs, concentrated time
accounts_hit = len(data['target_accounts'])
unique_ips = len(data['source_ips'])
if accounts_hit >= threshold:
# High ratio of targeted accounts to source IPs = spray
spray_score = accounts_hit / max(unique_ips, 1)
if spray_score >= 5: # 5+ accounts per IP = likely spray
alerts.append({
'type': 'password_spray_detected',
'severity': 'critical' if accounts_hit > 100 else 'high',
'window_start': window_start,
'accounts_targeted': accounts_hit,
'source_ips': list(data['source_ips']),
'total_failures': data['failed_count'],
'spray_score': round(spray_score, 1)
})
return alerts
import re
import hashlib
import requests
from datetime import datetime
class PasswordPolicyChecker:
"""Enterprise password policy with spray-resistant rules"""
def __init__(self, company_name):
self.company_name = company_name.lower()
self.company_variants = self._generate_variants(company_name)
def _generate_variants(self, name):
"""Generate common leetspeak and pattern variants"""
base = name.lower()
variants = {base}
# Common substitutions
subs = {'a': '@4', 'e': '3', 'i': '1!', 'o': '0', 's': '$5'}
for char, replacements in subs.items():
for r in replacements:
variants.add(base.replace(char, r))
return variants
def check(self, password):
"""Returns (is_valid, rejection_reason)"""
pw_lower = password.lower()
# Block company name variants
for variant in self.company_variants:
if variant in pw_lower:
return False, 'Password contains company name'
# Block seasonal patterns
seasons = ['spring', 'summer', 'autumn', 'fall', 'winter']
current_year = datetime.now().year
for season in seasons:
for year in range(current_year - 2, current_year + 2):
if f'{season}{year}' in pw_lower:
return False, 'Password contains predictable seasonal pattern'
# Block common base passwords
common_bases = [
'password', 'welcome', 'qwerty', 'admin', 'changeme',
'letmein', 'monkey', 'dragon', 'master', 'login'
]
for base in common_bases:
if base in pw_lower:
return False, 'Password contains common word'
# Block keyboard walks
keyboard_walks = [
'qwerty', 'qwer', 'asdf', 'zxcv', '1234', '4321',
'qazwsx', 'qwertyui'
]
for walk in keyboard_walks:
if walk in pw_lower:
return False, 'Password contains keyboard pattern'
# Check HaveIBeenPwned (k-anonymity API)
if self._check_hibp(password):
return False, 'Password found in known data breaches'
return True, None
def _check_hibp(self, password):
"""Check password against HaveIBeenPwned using k-anonymity"""
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
resp = requests.get(f'https://api.pwnedpasswords.com/range/{prefix}')
return suffix in resp.text
PowerWAF automatically blocks Password Spraying Attack at the edge.
Deploy in minutes. No code changes required. Free plan available.
Free plan spots are limited