Query Details
id: c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f
name: ADFS Low-and-Slow Password Spray - Rate-Evasion Pattern
version: 1.0.0
kind: Scheduled
description: |
Detects IP addresses performing a low-volume, slow-cadence credential attack against ADFS:
2–15 failures per hour spread across more than 5 unique accounts. This "low-and-slow" technique
deliberately stays under per-account lockout thresholds and ADFS rate limits, making it nearly
invisible to volume-based alerting. The query measures persistence across multiple hours to
identify IPs with consistent spray behavior.
MITRE ATT&CK: T1110 (Brute Force - Password Spraying)
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- ADFSSignInLogs
queryFrequency: 6h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1110
query: |
let SprayErrors = dynamic(["50126", "50034", "50053", "396083"]);
ADFSSignInLogs
| where TimeGenerated > ago(1d)
| extend ErrorCode = tostring(ResultType)
| where ErrorCode in (SprayErrors)
| summarize
FailsPerHour = count(),
UniqueUsers = dcount(UserPrincipalName)
by IPAddress, bin(TimeGenerated, 1h)
| where FailsPerHour between (2 .. 15)
and UniqueUsers > 5
| summarize
SprayHours = count(),
TotalAttempts = sum(FailsPerHour),
TotalUsers = sum(UniqueUsers)
by IPAddress
| where SprayHours >= 3
| order by SprayHours desc
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
customDetails:
SprayHours: SprayHours
TotalAttempts: TotalAttempts
TotalUsers: TotalUsers
alertDetailsOverride:
alertDisplayNameFormat: "ADFS Low-and-Slow Spray from {{IPAddress}} - {{SprayHours}} hours of activity"
alertDescriptionFormat: "IP {{IPAddress}} performed {{TotalAttempts}} credential failures across multiple ADFS accounts over {{SprayHours}} hours at a rate designed to evade lockout."
alertSeverityColumnName: ""
alertTacticsColumnName: ""
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT12H
matchingMethod: AnyAlert
groupByEntities:
- IP
groupByAlertDetails: []
groupByCustomDetails: []
This query is designed to detect a specific type of cyber attack known as a "low-and-slow" password spray against Active Directory Federation Services (ADFS). Here's a simple breakdown of what it does:
Purpose: It identifies IP addresses that are attempting to guess passwords by making a small number of login attempts (2 to 15 failures per hour) across more than 5 different user accounts. This method is used to avoid triggering account lockouts and bypass ADFS rate limits.
Data Source: The query uses data from ADFS sign-in logs, specifically looking for certain error codes that indicate failed login attempts.
Detection Criteria:
Alerting: If an IP address meets these criteria, it triggers an alert. The alert includes details like the number of hours the activity was observed, the total number of failed attempts, and the number of unique users targeted.
Severity and Tactics: The severity of the alert is set to medium, and it aligns with tactics like Credential Access and Initial Access as per the MITRE ATT&CK framework.
Incident Management: The query is scheduled to run every 6 hours and will create an incident if the conditions are met. It groups incidents by IP address to manage related alerts together.
Overall, this query helps security teams identify and respond to stealthy password spray attacks that might otherwise go unnoticed due to their low volume and slow pace.

David Alonso
Released: March 24, 2026
Tables
Keywords
Operators