Microsoft 365 distributed sign-in spray
Repeated sign-in failures for ONE account from many addresses inside a single network prefix, over days rather than minutes.
What it detects
Repeated sign-in failures for ONE account from many addresses inside a single network prefix, over days rather than minutes.
This is credential stuffing shaped specifically to defeat per-address thresholds: one attempt per address, a fresh address each time, all rented from the same block. On the motivating case 16 distinct IPv6 addresses inside one 2a0a:d683::/32 each failed exactly once over two weeks. Every existing rule missed it by construction — entra_signin_brute_force groups by [source_ip, user_name], entra_password_spray by [source_ip], and signin_many_source_ips wants 8 addresses in an hour on SUCCESS. All three saw a count of one, sixteen times.
Grouping by prefix rather than address makes the actor visible: the addresses differ, the actor does not. The window is deliberately long because the whole point of the technique is to be slow.
MITRE ATT&CK mapping
- T1110.003Brute Force: Password Spraying· Credential Access
False positives and triage
A user with a stale password on several devices behind one carrier will produce failures from one prefix. Check whether the addresses are a residential carrier or a hosting/VPN range, whether the failures are spread evenly over days (a human retries in bursts), and whether any attempt eventually succeeded. A prefix belonging to the tenant's own VPN should be allowlisted once confirmed.
The rule
Declarative YAML read by the detection engine — data, never executed. Confidence 60%. Source: rules/cloud/m365_distributed_signin_spray.yaml, licensed under the Apache License 2.0.
id: cloud.identity.m365_distributed_signin_spray
version: 1.0.0
status: stable
severity: high
confidence: 0.6
platforms: [application]
mitre_techniques: [T1110.003]
description: |
Repeated sign-in failures for ONE account from many addresses inside a single
network prefix, over days rather than minutes.
This is credential stuffing shaped specifically to defeat per-address
thresholds: one attempt per address, a fresh address each time, all rented from
the same block. On the motivating case 16 distinct IPv6 addresses inside one
`2a0a:d683::/32` each failed exactly once over two weeks. Every existing rule
missed it by construction — `entra_signin_brute_force` groups by
`[source_ip, user_name]`, `entra_password_spray` by `[source_ip]`, and
`signin_many_source_ips` wants 8 addresses in an hour on SUCCESS. All three saw
a count of one, sixteen times.
Grouping by prefix rather than address (ADR-053) makes the actor visible: the
addresses differ, the actor does not. The window is deliberately long because
the whole point of the technique is to be slow.
match:
event_category: authentication
event_outcome: failure
threshold:
count: 5
window_seconds: 604800
# /48 is the usual IPv6 site allocation and /24 the smallest routed IPv4 block,
# but one spec must serve both here; /32 is the conservative choice — it groups
# an IPv6 provider block without merging unrelated IPv4 networks, since a /32
# IPv4 prefix is a single address and so behaves exactly as before.
group_by: [user_name, source_ip/32]
correlation_keys: [user_name]
false_positive_guidance: |
A user with a stale password on several devices behind one carrier will produce
failures from one prefix. Check whether the addresses are a residential carrier
or a hosting/VPN range, whether the failures are spread evenly over days (a
human retries in bursts), and whether any attempt eventually succeeded. A prefix
belonging to the tenant's own VPN should be allowlisted once confirmed.
Related detections
Run this rule on your own logs — offline
KIROSEC runs this rule and the rest of the library over Windows, Linux, macOS and cloud audit logs on the analyst's own machine. Every alert links to the events that raised it, and nothing leaves the device.
