Distributed password spray

Severity highCross-platformThresholdstablemulti.authentication.distributed_password_spray · v1.0.0

Many DISTINCT accounts failed authentication across the tenant in a short window — a password spray, counted the only way a spray can be counted.

What it detects

Many DISTINCT accounts failed authentication across the tenant in a short window — a password spray, counted the only way a spray can be counted.

A spray tries one or two common passwords against a large list of accounts. Modern ones distribute the attempts across many source addresses as well, and the result defeats every per-entity threshold by construction:

* group by USER → one or two failures per account. Under any sane threshold. * group by SOURCE IP → one or two failures per address. Under any sane threshold.

Both of our existing brute-force rules group by one of those axes, so a distributed spray is invisible to them by design, not by oversight. Measured on a benchmark corpus: 30 accounts, 29 distinct source addresses, every attempt a failure, all inside 25 seconds — and not one rule fired.

The signal only exists at the level of the tenant: the BREADTH of accounts being probed at once. Nobody legitimately fails to authenticate as twenty different people in five minutes. So this rule counts distinct accounts tenant-wide, which is what threshold.group_by: [] means.

MITRE ATT&CK mapping

  • T1110.003Brute Force: Password Spraying· Credential Access

False positives and triage

The two things that genuinely look like this are a mass password-policy change and an outage: after a forced reset, or when a directory or MFA provider is failing, many accounts fail at once from ordinary locations. Check whether the failures carry a credential error (a spray does) or a service/timeout error (an outage does), and whether the sources are the organisation's own egress addresses.

Onboarding a new SSO integration can also produce a broad burst as clients re-authenticate. Correlate with change records.

The count of 20 in 5 minutes is set to sit above ordinary human error at small-to-mid tenant size; a very large tenant may legitimately exceed it during business-hours peaks and should raise it after measuring its own baseline. Read the ratio too: a spray shows near-100% failure across the accounts it touched, while an outage shows failures mixed with successes.

Because this counts tenant-wide, one alert describes the whole spray rather than one per account — that is deliberate, and the evidence lists every account probed.

The rule

Declarative YAML read by the detection engine — data, never executed. Confidence 70%. Source: rules/multi/distributed_password_spray.yaml, licensed under the Apache License 2.0.

id: multi.authentication.distributed_password_spray
version: 1.0.0
status: stable
severity: high
confidence: 0.7
platforms: [windows, linux, macos, network, application]
mitre_techniques: [T1110.003]
description: |
  Many DISTINCT accounts failed authentication across the tenant in a short window —
  a password spray, counted the only way a spray can be counted.

  A spray tries one or two common passwords against a large list of accounts. Modern
  ones distribute the attempts across many source addresses as well, and the result
  defeats every per-entity threshold by construction:

    * group by USER  → one or two failures per account. Under any sane threshold.
    * group by SOURCE IP → one or two failures per address. Under any sane threshold.

  Both of our existing brute-force rules group by one of those axes, so a distributed
  spray is invisible to them by design, not by oversight. Measured on a benchmark
  corpus: 30 accounts, 29 distinct source addresses, every attempt a failure, all
  inside 25 seconds — and not one rule fired.

  The signal only exists at the level of the tenant: the BREADTH of accounts being
  probed at once. Nobody legitimately fails to authenticate as twenty different people
  in five minutes. So this rule counts distinct accounts tenant-wide, which is what
  `threshold.group_by: []` means.
match:
  event_category: authentication
  event_outcome: failure
threshold:
  # Distinct ACCOUNTS, not events: twenty failures for one unlucky user is a
  # different finding (and a different rule).
  distinct: user_name
  count: 20
  window_seconds: 300
  # Tenant-wide. Grouping by anything is what a spray is built to evade.
  group_by: []
correlation_keys: [source_ip]
false_positive_guidance: |
  The two things that genuinely look like this are a mass password-policy change and
  an outage: after a forced reset, or when a directory or MFA provider is failing,
  many accounts fail at once from ordinary locations. Check whether the failures carry
  a credential error (a spray does) or a service/timeout error (an outage does), and
  whether the sources are the organisation's own egress addresses.

  Onboarding a new SSO integration can also produce a broad burst as clients
  re-authenticate. Correlate with change records.

  The count of 20 in 5 minutes is set to sit above ordinary human error at
  small-to-mid tenant size; a very large tenant may legitimately exceed it during
  business-hours peaks and should raise it after measuring its own baseline. Read
  the ratio too: a spray shows near-100% failure across the accounts it touched,
  while an outage shows failures mixed with successes.

  Because this counts tenant-wide, one alert describes the whole spray rather than
  one per account — that is deliberate, and the evidence lists every account probed.

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.

Browse all detections