Skip to main content
Question

Use metrics functions for Risk Analytics rules | target.user.userid, principal.ip_geo_artifact.network.organization_name

  • March 20, 2026
  • 4 replies
  • 128 views

BriMstone
Forum|alt.badge.img+1

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

 }

 

 

4 replies

BriMstone
Forum|alt.badge.img+1
  • Author
  • Bronze 1
  • March 20, 2026

I also tried stripping down the rule so that it is less complicated and the metric count still does not work, you can see the outcome count is still 0, when the alerts make it clear 2 countries are present. 

 

rule ueba_006_anomalous_auth_attempts_different_countries {


 

  meta:

 

    author = "GSO"

    description = "Demonstrates use of num_unique_filter_values by detecting where a user has attempted to authenticate from different countries"

    severity = "Low"

 

  events:

    //$attempt.metadata.event_type = "USER_LOGIN"

    $attempt.metadata.log_type = "DUO_AUTH"

    $attempt.target.user.userid != ""

    $attempt.target.user.userid = $userid

    $attempt.target.user.userid = "<redacted>"

 

   match:

    $userid over 12h

 

  outcome:

    $outcome_variable = max(metrics.auth_attempts_total(

        period: 1d,

        window: 30d,

    // This metric type indicates any filter with a wildcard value should be

    // aggregated over each day to produce a new metric on-the-fly.

        metric: num_unique_filter_values,

        agg: max,

        target.user.userid: $userid,

    // Filter whose value should be counted over each day to produce the

    // num_unique_filter_values metric.

        principal.ip_geo_artifact.location.country_or_region: *

))

 

  condition:

    $attempt

}

 

 

 


BriMstone
Forum|alt.badge.img+1
  • Author
  • Bronze 1
  • March 25, 2026

@ivan_ninichuck calling the Google god/favorite Google SA - if you have any thoughts at all, we are really stuck on this one! 


ipninichuck
Staff
Forum|alt.badge.img+5
  • Staff
  • March 25, 2026

First of all very happy to hear from you. Thank you for the compliment. I am taking a look at your rule for you. I will get you some insight asap. 

 

 


ar3diu
Forum|alt.badge.img+9
  • Silver 2
  • May 4, 2026

@BriMstone did you find a fix for your rule?

I’m struggling with something similar where I want to detect first time seen ASN for userid in JumpCloud logs. Getting lots of false positives where `$first_seen_asn = 0` although there are user login events with the same ASN from 7 days ago… 

$e1.metadata.log_type = "JUMPCLOUD_DIRECTORY_INSIGHTS"
$e1.metadata.event_type = "USER_LOGIN"
$e1.target.user.userid != ""
$user = $e1.target.user.userid
$asn = $e1.principal.ip_geo_artifact.network.organization_name
match: $user, $asn over 1h
outcome:
$first_seen_asn = max(metrics.auth_attempts_success(
period:1d,
window:30d,
metric:first_seen,
agg:max,
target.user.userid:$user,
principal.ip_geo_artifact.network.organization_name:$asn))
condition:
$e1 and $first_seen_asn = 0