Query Details

Security Event Password Spray Attack Through Kerberos

Query

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

Explanation

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.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: May 25, 2023

Tables

SecurityEvent

Keywords

SecurityEvent,TimeGenerated,EventID,IpAddress,TargetSid,Status,Result,PreviousTimeGenerated,CurrentTimeGenerated,Failure,StartTime,EndTime,FailureAccountCount,SuccessAccountCount,FailureAccounts,SuccessAccounts,Computers,Activities,Statuses,TargetDomainName,AlertName,AlertSeverity

Operators

letdynamicSecurityEventwhereinextendtrim_startnotasjoinkind=leftsemiiffevaluateactivity_counts_metricssummarizemake_bagpackarg_minarg_maxtointisnotemptyonminmaxdcountifarray_sort_ascmake_set_ifmake_settake_anyhasstrcatcasearray_lengthmv-expandtypeofproject

Actions