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 > 5
   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 when a user account experiences multiple failed sign-in attempts due to brute-force-related errors (like wrong passwords or locked accounts) followed by a successful sign-in. This pattern suggests that an attacker may have guessed or used the correct credentials.

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

  3. Time Frame: It examines sign-in attempts within the last 3 hours and runs every hour.

  4. Error Codes: It looks for specific error codes that are commonly associated with brute force attacks.

  5. Process:

    • It first identifies accounts with multiple failed sign-in attempts (more than 5) using the specified error codes.
    • Then, it checks if there was a successful sign-in for the same account after the last failed attempt.
    • It calculates the time between the last failed attempt and the first successful sign-in.
  6. Output: The query outputs details such as the user account name, the number of failed attempts, the time of the last failure, the time of the first success, the IP addresses involved, and the countries from which the failed attempts originated.

  7. Alerting: If such a pattern is detected, it triggers an alert with a high severity level. The alert includes details about the user account and the nature of the sign-in attempts.

  8. Incident Management: It can create an incident in the system for further investigation, grouping alerts by account to manage related incidents effectively.

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

Details

David Alonso profile picture

David Alonso

Released: May 29, 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