Securityevent Password Spray Attack Through Kerberos
KQL detection rule by ep3p · MIT
No description provided
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