Query Details
let query_frequency = 15m;
let query_period = 30m;
let query_wait = 15m;
let account_threshold = 5;
let _ExpectedIPAddresses = dynamic([]);
SecurityEvent
| where TimeGenerated between (ago(query_period + query_wait) .. ago(query_wait))
| where EventID in (4768, 4771)
| extend IpAddress = trim_start("::ffff:", IpAddress)
| where not(IpAddress in (_ExpectedIPAddresses))
| as _Events
| join kind=leftsemi (
_Events
| extend Result = iff(EventID == 4768 and Status == "0x0", "Success", "Failure")
| evaluate activity_counts_metrics(TargetSid, TimeGenerated, ago(query_period + query_wait), ago(query_wait), query_frequency, Result, IpAddress)
| summarize
Results = make_bag(pack(Result, ["new_dcount"]))
by TimeGenerated, IpAddress
| summarize
PreviousTimeGenerated = arg_min(TimeGenerated, PreviousResults = Results),
CurrentTimeGenerated = arg_max(TimeGenerated, CurrentResults = Results)
by IpAddress
| where CurrentTimeGenerated > ago(query_period + query_wait)
| extend PreviousResults = iff(PreviousTimeGenerated == CurrentTimeGenerated, dynamic([]), PreviousResults)
// Remove cases where distinct accounts with failures don't surpass the threshold
| where CurrentResults["Failure"] > account_threshold
or (isnotempty(PreviousResults["Failure"]) and not(PreviousResults["Failure"] > account_threshold) and (toint(PreviousResults["Failure"]) + toint(CurrentResults["Failure"])) > account_threshold)
)
on IpAddress
| summarize
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
FailureAccountCount = dcountif(TargetSid, not(EventID == 4768 and Status == "0x0")),
SuccessAccountCount = dcountif(TargetSid, EventID == 4768 and Status == "0x0"),
FailureAccounts = array_sort_asc(make_set_if(TargetUserName, not(EventID == 4768 and Status == "0x0"), 250)),
SuccessAccounts = array_sort_asc(make_set_if(TargetUserName, EventID == 4768 and Status == "0x0")),
Computers = array_sort_asc(make_set(Computer)),
Activities = array_sort_asc(make_set(Activity)),
Statuses = array_sort_asc(make_set_if(Status, not(EventID == 4768 and Status == "0x0"))),
take_any(TargetDomainName)
by IpAddress
// Remove IP addresses that don't have an authentication failure by wrong password
| where Statuses has "0x18"
| extend
AlertName = strcat(
"Password spray attack through Kerberos",
case(
array_length(SuccessAccounts) > 0, " - Compromised account",
""
)
),
AlertSeverity = case(
array_length(SuccessAccounts) > 0, "High",
not(array_length(SuccessAccounts) > 0), "Medium",
"Informational"
)
// If an account is believed to be compromised, expand the results, so it appears in Entities
| mv-expand SuccessAccount = iff(AlertName has " - Compromised account", SuccessAccounts, dynamic([""])) to typeof(string)
| project
StartTime,
EndTime,
Computers,
IpAddress,
Activities,
Statuses,
TargetDomainName,
FailureAccountCount,
SuccessAccountCount,
SuccessAccount,
SuccessAccounts,
FailureAccounts,
AlertName,
AlertSeverity
This query is designed to detect potential password spray attacks through Kerberos, a network authentication protocol.
It sets up several parameters, including the frequency, period, and wait time for the query, as well as a threshold for account failures. It also sets up a list of expected IP addresses.
The query then pulls security events within a certain time frame and filters for specific event IDs. It trims the IP address and excludes any that are in the expected list.
The query then joins these events with a subset of events that have been evaluated for activity counts and summarized by time and IP address. It filters for cases where the current time is greater than the query period plus wait time and where the number of failures exceeds the set threshold.
The query then summarizes the data by start and end time, count of failure and success accounts, computers, activities, and statuses. It also filters for IP addresses that have an authentication failure due to a wrong password.
Finally, it sets up an alert name and severity based on whether any accounts are believed to be compromised. If an account is compromised, it expands the results to include it in the entities. The query then projects the final results, including start and end times, computers, IP addresses, activities, statuses, domain name, account counts, accounts, alert name, and alert severity.

Jose Sebastián Canós
Released: May 25, 2023
Tables
Keywords
Operators