SigninLogs — Privileged Account Under Attack (Low-Threshold Failures)
06 SIGNIN Privileged Account Attack
Query
// ---- 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 failureThreshold = 3;
let privilegedKeywords = dynamic([
"admin", "administrator", "root", "global", "security",
"privileged", "svc", "elevated", "tier0",
"da-", "ea-", "ga-", "pa-"]);
SigninLogs
| invoke _ExcludeAllowlistedIPs()
| where TimeGenerated > ago(1h)
| where ResultType != "0"
| where UserPrincipalName has_any (privilegedKeywords)
or UserPrincipalName contains "_adm"
or UserPrincipalName startswith "adm"
| summarize
FailedAttempts = count(),
UniqueIPs = dcount(IPAddress),
SourceIPs = make_set(IPAddress, 20),
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
Locations = make_set(Location, 5),
Apps = make_set(AppDisplayName, 5),
FailureReasons = make_set(ResultDescription, 5)
by UserPrincipalName
| where FailedAttempts >= failureThreshold
| project
TimeGenerated = StartTime,
UserPrincipalName, FailedAttempts, UniqueIPs,
SourceIPs, Locations, Apps, FailureReasons, EndTimeExplanation
This query is designed to detect potential attacks on privileged accounts by monitoring sign-in failures. Here's a simplified breakdown of what it does:
-
Purpose: The query aims to identify failed login attempts on accounts with administrative or privileged naming conventions. It uses a lower threshold (3 failures) than typical brute force detection rules because attacks on privileged accounts are considered high-priority.
-
Data Source: It uses data from Azure Active Directory's SigninLogs.
-
Frequency: The query runs every hour and looks at data from the past hour.
-
Exclusions: It excludes IP addresses that are on a predefined allowlist, which means trusted IPs are not considered in the analysis.
-
Detection Criteria:
- It looks for failed login attempts (ResultType not equal to "0") on accounts with names containing keywords like "admin", "administrator", "root", etc.
- It counts the number of failed attempts and considers it suspicious if there are 3 or more failures.
-
Output: For each suspicious account, it provides:
- The number of failed attempts.
- The number of unique IP addresses involved.
- A list of source IPs, locations, applications used, and failure reasons.
- The time range during which these attempts occurred.
-
Alerting: If the criteria are met, an alert is generated with details about the failed attempts, including the number of attempts and unique IPs.
-
Incident Management: The query can create incidents in a security management system, grouping related alerts to manage them more effectively.
Overall, this query helps security teams quickly identify and respond to potential brute force attacks on sensitive accounts, prioritizing those with administrative privileges.
Details

David Alonso
Released: April 20, 2026
Tables
Keywords
Operators