Query Details
let query_frequency = 1h;
let query_period = 14d;
let user_threshold = 10;
let failures_threshold = 3;
let _UnexpectedNonInteractiveFailures = toscalar(
_GetWatchlist("ResultType-SignInLogsErrorCodes")
| where Notes has "[Failure]" and (Notes has_all ("[Password]", "[Invalid]") or Notes has "[Malicious]") and isnotempty(ResultDescription) // For example 50126, 50053, 500532
| summarize make_list(ResultType)
);
let _SuccessResultTypes = toscalar(
_GetWatchlist("ResultType-SignInLogsErrorCodes")
| where Notes has_any ("[Expired]", "[Success]") and isnotempty(ResultDescription)
| summarize make_list(ResultType)
);
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(query_period)
| where ResultType in (_UnexpectedNonInteractiveFailures) and HomeTenantId == ResourceTenantId
| summarize
FailureStartTime = min(TimeGenerated),
FailureEndTime = max(TimeGenerated),
FailureEventCount = count(),
FailureUserCount = dcount(UserId)
by AppDisplayName, AppId, ResourceDisplayName, ResourceIdentity
| where isnotempty(AppId) or isnotempty(ResourceIdentity)
| where FailureUserCount > user_threshold and FailureEventCount >= failures_threshold * user_threshold
| lookup kind=leftouter (
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(query_frequency)
| where ResultType in (_SuccessResultTypes)
| summarize
SuccessStartTime = min(TimeGenerated),
SuccessEndTime = max(TimeGenerated),
SuccessEventCount = count(),
SuccessUserCount = dcount(UserId),
SuccessUsers = array_sort_asc(make_set(UserPrincipalName))
by AppId, ResourceIdentity
) on AppId, ResourceIdentity
| where case(
array_length(SuccessUsers) > 0, true, // Authentication success or partial success
FailureStartTime > ago(query_frequency), true, // First password spray in last query_frequency
false
)
| extend
StartTime = min_of(FailureStartTime, SuccessStartTime),
Endtime = max_of(FailureEndTime, SuccessEndTime),
AlertSeverity = case(
array_length(SuccessUsers) > 0, "High", // Authentication success or partial success
FailureStartTime > ago(query_frequency), "Informational", // First password spray in last query_frequency
"High"
)
| project
StartTime,
Endtime,
AppDisplayName,
ResourceDisplayName,
FailureEventCount,
FailureUserCount,
SuccessEventCount,
SuccessUserCount,
SuccessUsers,
AppId,
ResourceIdentity,
AlertSeverity
This query is designed to detect and analyze unusual non-interactive sign-in failures in Azure Active Directory (AAD) over a specified period. Here's a simplified breakdown:
Parameters:
query_frequency: The recent time window (1 hour) for checking successful sign-ins.query_period: The overall time period (14 days) for analyzing sign-in failures.user_threshold and failures_threshold: Thresholds for identifying significant failure events.Watchlists:
_UnexpectedNonInteractiveFailures: A list of error codes indicating unexpected non-interactive sign-in failures, such as password issues or malicious attempts._SuccessResultTypes: A list of error codes indicating successful or expired sign-ins.Failure Analysis:
Success Analysis:
Alert Generation:
Output:
In essence, this query helps identify potential password spray attacks or other suspicious activities by correlating failure and success sign-in events over a specified period.

Jose Sebastián Canós
Released: January 24, 2025
Tables
Keywords
Operators