Query Details

03 ADFS Low And Slow Spray

Query

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: []

Explanation

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:

  1. 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.

  2. Data Source: The query uses data from ADFS sign-in logs, specifically looking for certain error codes that indicate failed login attempts.

  3. Detection Criteria:

    • It examines login attempts over the past day.
    • It counts the number of failed attempts per hour and the number of unique user accounts targeted.
    • It flags IP addresses that have at least 3 hours of such activity within the day.
  4. 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.

  5. 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.

  6. 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.

Details

David Alonso profile picture

David Alonso

Released: March 24, 2026

Tables

ADFSSignInLogs

Keywords

ADFSAzureActiveDirectoryIPAddressUserPrincipalNameTimeGeneratedResultType

Operators

letdynamicwhereagoextendtostringinsummarizecountdcountbybinbetweenandorder bydesc

Actions