Users attempting to brute force Azure AD credentials by generating excessive authentication failure events may indicate a coordinated attack effort, and proactive hunting for such high-volume failure patterns helps identify potential credential compromise before account lockouts or lateral movement occur. This behavior aligns with T1110 and is critical to detect early in Azure Sentinel to mitigate unauthorized access risks.
KQL Query
let successCodes = dynamic(["0", "50125", "50140", "70043", "70044"]);
let aadFunc = (tableName:string){
table(tableName)
| extend FailureOrSuccess = iff(ResultType in (successCodes), "Success", "Failure")
| summarize FailureCount = countif(FailureOrSuccess=="Failure"), SuccessCount = countif(FailureOrSuccess=="Success") by bin(TimeGenerated, 1h),UserPrincipalName, UserDisplayName, IPAddress
| where FailureCount > 100
| where SuccessCount > 0
| order by UserPrincipalName, TimeGenerated asc
| extend AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
id: 056ceb9b-8f07-42b3-853e-ef3779de222e
name: Suspected Brute force attack Investigation
description: |
'Summarize all the failures and success events for all users in the last 24 hours,
only identify users with more than 100 failures in the set period'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let successCodes = dynamic(["0", "50125", "50140", "70043", "70044"]);
let aadFunc = (tableName:string){
table(tableName)
| extend FailureOrSuccess = iff(ResultType in (successCodes), "Success", "Failure")
| summarize FailureCount = countif(FailureOrSuccess=="Failure"), SuccessCount = countif(FailureOrSuccess=="Success") by bin(TimeGenerated, 1h),UserPrincipalName, UserDisplayName, IPAddress
| where FailureCount > 100
| where SuccessCount > 0
| order by UserPrincipalName, TimeGenerated asc
| extend AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
| Sentinel Table | Notes |
|---|---|
AADNonInteractiveUserSignInLogs | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Scenario: Automated system backup job using rsync or cron with repeated authentication attempts
Filter/Exclusion: Exclude events related to scheduled tasks with known credentials, e.g., user = "backup_user" AND event_type = "scheduled_job"
Scenario: Regular user logins via SSH using sudo for administrative tasks
Filter/Exclusion: Exclude logins from known admin users during business hours, e.g., user = "admin_user" AND time_between("08:00", "18:00")
Scenario: Failed login attempts from internal IP ranges due to misconfigured firewall rules
Filter/Exclusion: Exclude IP addresses within the internal network, e.g., source_ip = "192.168.0.0/16"
Scenario: Failed authentication attempts during a password reset process using passwd or chpasswd
Filter/Exclusion: Exclude events where the user is attempting to reset their own password, e.g., user = "target_user" AND event_type = "password_reset_attempt"
Scenario: Failed login attempts from a monitoring tool like Nagios or Zabbix during system checks
Filter/Exclusion: Exclude events from known monitoring tools, e.g., source_ip = "nagios_server_ip" OR source_ip = "zabbix_server_ip"