I have the following rule, but the $historical_threshold_network_success and $historical_threshold_state_success are not counting the historical networks or states, leading to FPs where the detection triggers for user’s where the $historical_threshold_network_success is NOT unique (see attachment image for example). What do I need to do to make $historical_threshold_network_success count correctly? The goal is to reduce FP’s by telling the rule “if specific user has a Duo login history where the ISP (organization) is seen 1 or less times, detect”. This will reduce FPs by baselining user’s typical ISP and only detecting on anomalous ISPs that successfully authenticated.
rule Duo_ATO_ActivelyCompromised {
meta:
author = "Me"
description = "Detects a risky blocked Duo login followed by a successful, risky Duo login (potential ATO) from an access device in a state that has not been seen in the last 30 days."
events:
// Exclude events where the authentication device is in Arizona
$blocked_login.principal.ip_geo_artifact.location.state != "Arizona"
$allowed_login.principal.ip_geo_artifact.location.state != "Arizona"
// Event Type 1 - The Blocked Login Attempt
$blocked_login.metadata.log_type = "DUO_AUTH"
$blocked_login.metadata.event_type = "USER_LOGIN"
$blocked_login.security_result.action = "BLOCK"
$targetAccountId = $blocked_login.target.user.userid
// Look for Duo attack detectors on the blocked login
$blocked_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "UNREALISTIC_GEOVELOCITY" OR
$blocked_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "COUNTRY_CODE_MISMATCH" OR
$blocked_login.extracted.fields["adaptive_trust_assessments.remember_me.reason"] = "Novel Access IP" OR
$blocked_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "PUSH_HARRASSMENT" OR
$blocked_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "CONSECUTIVE_FAILURES" AND
$blocked_login.additional.fields["reason"] = "Low level of trust; detection of one or more known attack patterns."
// Event Type 2 - Allowed Login Attempt
$allowed_login.metadata.log_type = "DUO_AUTH"
$allowed_login.metadata.event_type = "USER_LOGIN"
$allowed_login.security_result.action = "ALLOW"
$targetAccountId = $allowed_login.target.user.userid
// Look for Duo attack detectors on the allowed login
$allowed_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "UNREALISTIC_GEOVELOCITY" OR
$allowed_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "COUNTRY_CODE_MISMATCH" OR
$allowed_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "PUSH_HARRASSMENT" OR
$allowed_login.extracted.fields["rbfs_triggered_attacks[0].detector"] = "CONSECUTIVE_FAILURES" OR
$allowed_login.extracted.fields["adaptive_trust_assessments.remember_me.reason"] = "Novel Access IP" AND
$allowed_login.additional.fields["reason"] = "Low level of trust; detection of one or more known attack patterns."
// The block must happen before the allowed login
$blocked_login.metadata.event_timestamp.seconds < $allowed_login.metadata.event_timestamp.seconds
// Require the same source IP for both events
$blocked_login.principal.ip = $allowed_login.principal.ip
// Define state variable from the login for metric lookups
$allowed_login.principal.ip_geo_artifact.location.state = $ip_state
$allowed_login.principal.ip_geo_artifact.network.organization_name = $ip_network
//$blocked_login.principal.ip_geo_artifact.location.state = $ip_state
//$blocked_login.principal.ip_geo_artifact.network.organization_name = $ip_network
match:
$targetAccountId over 1h
outcome:
$failed_logins_count = count_distinct($blocked_login.metadata.id)
$user = array_distinct($allowed_login.target.user.userid)
// Baseline: successful logins for this account over the past 30 days
$historical_login_count = max(metrics.auth_attempts_success(
period:1d, window:30d,
metric:event_count_sum, agg:sum,
target.user.userid:$targetAccountId
))
// Baseline: amount of times the source network for the network has been seen in successful logins
$historical_threshold_network_success = max(metrics.auth_attempts_success(
period:1d, window:30d,
metric:event_count_sum, agg:sum,
principal.ip_geo_artifact.network.organization_name:$ip_network,
target.user.userid:$targetAccountId
))
// Baseline: amount of times the source state has been seen in successful logins
$historical_threshold_state_success = max(metrics.auth_attempts_success(
period:1d, window:30d,
metric:event_count_sum, agg:sum,
principal.ip_geo_artifact.location.country_or_region:$ip_state,
target.user.userid:$targetAccountId
))
$network_name = array_distinct($allowed_login.principal.ip_geo_artifact.network.organization_name)
$network_count = count_distinct($allowed_login.principal.ip_geo_artifact.network.organization_name)
$state_names = array_distinct($allowed_login.principal.ip_geo_artifact.location.state)
// Risk Score logic
$risk_score = max(
if($allowed_login.extracted.fields["rbfs_triggered_attacks[0].detector"] != "", 20, 0) +
if($allowed_login.extracted.fields["rbfs_triggered_attacks[1].detector"] != "", 40, 0) +
if($allowed_login.extracted.fields["rbfs_triggered_attacks[2].detector"] != "", 60, 0) +
if($allowed_login.extracted.fields["rbfs_triggered_attacks[3].detector"] != "", 80, 0) +
if($blocked_login.extracted.fields["rbfs_triggered_attacks[0].detector"] != "", 5, 0) +
if($blocked_login.extracted.fields["rbfs_triggered_attacks[1].detector"] != "", 5, 0) +
if($blocked_login.extracted.fields["rbfs_triggered_attacks[2].detector"] != "", 5, 0) +
if($blocked_login.extracted.fields["rbfs_triggered_attacks[3].detector"] != "", 5, 0)
)
condition:
$blocked_login AND $allowed_login and $risk_score >= 20 and $historical_login_count >= 10 and $historical_threshold_network_success <= 1 //and $failed_logins_count >= 2
}

