Query Details

Brute Force Success - Credential Stuffing Succeeded

08 Brute Force Success Chain

Query

let BruteForceErrors = dynamic(["50126","50055","50056","50064","50053","50034","50057","50128"]);
let Failures =
    AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(3h)
    | extend ErrorCode = tostring(ResultType)
    | where ErrorCode in (BruteForceErrors)
    | summarize
        FailCount = count(),
        LastFail  = max(TimeGenerated),
        FailIPs   = make_set(IPAddress),
        Countries = make_set(Location)
      by UserPrincipalName;
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(3h)
| where ResultType == 0
| summarize
    FirstSuccess = min(TimeGenerated),
    SuccessIP    = tostring(make_set(IPAddress)[0])
  by UserPrincipalName
| join kind=inner Failures on UserPrincipalName
| where FailCount > 15
   and  FirstSuccess > LastFail
| project
    UserPrincipalName,
    FailCount,
    LastFail,
    FirstSuccess,
    TimeBetweenFailAndSuccess = (FirstSuccess - LastFail),
    FailIPs,
    SuccessIP,
    Countries
| order by FailCount desc

Explanation

This query is designed to detect successful brute force or credential stuffing attacks on user accounts. Here's a simple breakdown of what it does:

  1. Purpose: It identifies instances where a user account experiences multiple failed non-interactive sign-in attempts due to errors like wrong passwords or locked accounts, followed by a successful sign-in. This pattern suggests that an attacker might have guessed or used the correct credentials eventually.

  2. Data Source: It uses logs from Azure Active Directory, specifically focusing on non-interactive user sign-in logs.

  3. Time Frame: The query looks at sign-in attempts within the last 3 hours and runs every hour.

  4. Process:

    • It first identifies failed sign-in attempts with specific error codes related to brute force attacks.
    • It counts these failures for each user and notes the last failure time, IP addresses, and countries involved.
    • It then looks for successful sign-ins after these failures for the same user.
    • It checks if there were more than 15 failed attempts before a successful sign-in and ensures the successful sign-in happened after the last failure.
  5. Output: The query lists users who had a high number of failed attempts followed by a successful sign-in, along with details like the number of failures, time between the last failure and success, IP addresses used, and countries involved.

  6. Severity and Alerts: The severity of this detection is marked as high. If such a pattern is detected, an alert is generated with details about the user and the sign-in attempts. It also creates an incident for further investigation.

  7. Mapping and Custom Details: The query maps user accounts and IP addresses for easier identification and provides custom details like failure count and involved countries in the alert.

Overall, this query helps security teams quickly identify and respond to potential credential stuffing or brute force attacks on user accounts.

Details

David Alonso profile picture

David Alonso

Released: July 16, 2026

Tables

AADNonInteractiveUserSignInLogs

Keywords

AzureActiveDirectoryAADNonInteractiveUserSignInLogsUserPrincipalNameIPAddressLocationAccountIP

Operators

letdynamicinagoextendtostringsummarizecountmaxmake_setbywhere==joinkind=inneron>andproject-order bydesc

Severity

High

Tactics

CredentialAccessInitialAccess

MITRE Techniques

Frequency: 1h

Period: 3h

Actions

GitHub