Query Details
// Rule : Workload Identity - Service Principal Brute Force Authentication
// Severity: High
// Tactics : CredentialAccess
// MITRE : T1110.003 (Brute Force: Password Spraying), T1078.004
// Freq : PT1H Period: PT1H
//==========================================================================================
// --- Successful sign-ins in same window (for corroboration) ---
// ---- Network Allowlist (exclude trusted IPs / CIDR / ranges) --------------
let _allow = materialize(union isfuzzy=true (print R="" | take 0), (_GetWatchlist('NetworkAllowlist') | project R = tostring(IPOrRange)) | where isnotempty(R));
let _allowCIDR = toscalar(_allow | where not(R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$') | extend R = iff(R has '/', R, strcat(R, '/32')) | summarize make_list(R));
let _allowRange = toscalar(_allow | where R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$' | summarize make_list(R));
let _ExcludeAllowlistedIPs = (T:(IPAddress:string)) {
T
| extend IPAddress = tostring(IPAddress)
| where array_length(_allowCIDR) == 0 or isnull(ipv4_is_in_any_range(IPAddress, _allowCIDR)) or not(ipv4_is_in_any_range(IPAddress, _allowCIDR))
| mv-apply _r = _allowRange to typeof(string) on (
extend _lo = tostring(split(_r,'-')[0]), _hi = tostring(split(_r,'-')[1])
| extend _inRange = ipv4_compare(IPAddress, _lo) >= 0 and ipv4_compare(IPAddress, _hi) <= 0
| summarize _anyInRange = max(toint(_inRange)))
| where isnull(_anyInRange) or _anyInRange == 0
| project-away _anyInRange
};
// ---------------------------------------------------------------------------
let SuccessfulSignins = (AADServicePrincipalSignInLogs | invoke _ExcludeAllowlistedIPs())
| where TimeGenerated > ago(1h)
| where ResultType == "0"
| summarize SuccessCount = count(), SuccessIPs = make_set(IPAddress, 5)
by ServicePrincipalId;
// --- Failed authentications ---
(AADServicePrincipalSignInLogs | invoke _ExcludeAllowlistedIPs())
| where TimeGenerated > ago(1h)
| where ResultType != "0"
| where ResultType !in ("50076", "50079", "50058", "70044") // exclude MFA prompts, session expiry (FP)
| summarize
FailedCount = count(),
UniqueIPs = dcount(IPAddress),
IPList = make_set(IPAddress, 20),
ErrorCodes = make_set(ResultType, 10),
ErrorDescriptions = make_set(ResultDescription, 5),
Resources = make_set(ResourceDisplayName, 10),
CredTypes = make_set(ClientCredentialType, 5),
FirstAttempt = min(TimeGenerated),
LastAttempt = max(TimeGenerated)
by ServicePrincipalName, ServicePrincipalId, AppId
| where FailedCount > 20 or UniqueIPs > 5
| join kind=leftouter SuccessfulSignins on ServicePrincipalId
| extend
SpraySucceeded = SuccessCount > 0,
Severity = case(
SuccessCount > 0 and FailedCount > 20, "Critical",
FailedCount > 50, "High",
"Medium")
This query is designed to detect potential brute force attacks on service principals by analyzing authentication logs. Here's a simplified breakdown of what the query does:
Allowlist Setup: It first sets up a list of trusted IP addresses or ranges (allowlist) to exclude them from further analysis. This helps focus on potentially malicious activity from untrusted sources.
Successful Sign-ins: It checks for successful sign-ins from untrusted IPs within the last hour. It counts these successful attempts and notes the IPs involved, grouping them by the service principal ID.
Failed Authentications: It then looks for failed authentication attempts from untrusted IPs within the same time frame. It excludes certain error codes related to multi-factor authentication prompts and session expiries to avoid false positives.
Summary of Failed Attempts: For each service principal, it summarizes the number of failed attempts, the number of unique IPs involved, and other details like error codes, resources accessed, and credential types used. It also records the time of the first and last failed attempt.
Suspicious Activity Detection: It flags service principals with more than 20 failed attempts or more than 5 unique IPs involved. It then checks if any of these principals also had successful sign-ins, which could indicate a successful brute force attack.
Severity Assignment: Based on the number of failed attempts and whether any succeeded, it assigns a severity level to the potential threat: "Critical" if there are successful sign-ins after many failed attempts, "High" if there are more than 50 failed attempts, and "Medium" otherwise.
Overall, this query helps identify and prioritize potential security threats related to brute force attacks on service principals by analyzing authentication patterns and excluding trusted sources.

David Alonso
Released: April 21, 2026
Tables
Keywords
Operators