Adfssigninlogs Password Spray Attack Against Ad Fs
KQL detection rule by ep3p · MIT
No description provided
let query_frequency = 15m;
let query_period = 30m;
let query_wait = 30m;
let account_threshold = 10;
let _SuccessResultTypes = toscalar(
_GetWatchlist("ResultType-SignInLogsErrorCodes")
| where Notes has_any ("[Expired]", "[Success]") and isnotempty(ResultDescription)
| summarize make_list(ResultType)
);
ADFSSignInLogs
| where TimeGenerated between (ago(query_period + query_wait) .. ago(query_wait))
// | where not(todynamic(AuthenticationDetails)[0].authenticationMethod == "Integrated Windows Authentication")
// | lookup kind=leftouter (
// union SigninLogs, AADNonInteractiveUserSignInLogs
// | where TimeGenerated > ago(query_period + query_wait)
// | distinct CorrelationId, SecondaryIPAddress = IPAddress
// ) on CorrelationId
| extend
SuccessfulAuthentication = ResultType in (_SuccessResultTypes),
SummarizeKey = iff(ipv4_is_private(IPAddress) and not(UserAgent in ("", "-")), strcat(IPAddress, "<<>>", UserAgent), IPAddress)
| as _Events
| join kind=leftsemi (
_Events
| evaluate activity_counts_metrics(UserId, TimeGenerated, ago(query_period + query_wait), ago(query_wait), query_frequency, SuccessfulAuthentication, SummarizeKey)
| summarize
Results = make_bag(pack(iff(SuccessfulAuthentication, "Success", "Failure"), ["new_dcount"]))
by TimeGenerated, SummarizeKey
| summarize
PreviousTimeGenerated = arg_min(TimeGenerated, PreviousResults = Results),
CurrentTimeGenerated = arg_max(TimeGenerated, CurrentResults = Results)
by SummarizeKey
| 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)
// Remove cases where successes surpass failures and IP address is private and the UserAgent is empty
| where not(not(SummarizeKey has "<<>>") and (isnotempty(parse_ipv4(SummarizeKey)) and ipv4_is_private(SummarizeKey)) and toint(CurrentResults["Success"]) > toint(CurrentResults["Failure"]))
) on SummarizeKey
| summarize
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
FailureAccountCount = dcountif(UserPrincipalName, not(SuccessfulAuthentication)),
SuccessAccountCount = dcountif(UserPrincipalName, SuccessfulAuthentication),
FailureAccounts = array_sort_asc(make_set_if(UserPrincipalName, not(SuccessfulAuthentication), 250)),
SuccessAccounts = array_sort_asc(make_set_if(UserPrincipalName, SuccessfulAuthentication)),
take_anyif(Location, isnotempty(Location)),
take_any(IPAddress, UserAgent, TokenIssuerName)
by SummarizeKey
| extend
AlertName = strcat(
"Password spray attack against AD FS",
case(
array_length(SuccessAccounts) > 0 and (not(isnotempty(parse_ipv4(IPAddress)) and ipv4_is_private(IPAddress)) or isempty(parse_ipv4(IPAddress)) and isnotempty(parse_ipv6(IPAddress))), " - Compromised account",
array_length(SuccessAccounts) between (1 .. 5) and (isnotempty(parse_ipv4(IPAddress)) and ipv4_is_private(IPAddress)), " - Potentially compromised account",
""
)
),
AlertSeverity = case(
array_length(SuccessAccounts) > 0 and (not(isnotempty(parse_ipv4(IPAddress)) and ipv4_is_private(IPAddress)) or isempty(parse_ipv4(IPAddress)) and isnotempty(parse_ipv6(IPAddress))), "High",
array_length(SuccessAccounts) between (1 .. 5) and (isnotempty(parse_ipv4(IPAddress)) and ipv4_is_private(IPAddress)), "Medium",
not(array_length(SuccessAccounts) > 0) and (isnotempty(parse_ipv4(IPAddress)) and ipv4_is_private(IPAddress)), "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,
IPAddress,
Location,
UserAgent,
FailureAccountCount,
SuccessAccountCount,
SuccessAccount,
SuccessAccounts,
FailureAccounts,
TokenIssuerName,
AlertName,
AlertSeverity