diff --git a/scripts/base/frameworks/metrics/main.bro b/scripts/base/frameworks/metrics/main.bro index 38ed17b36f..e488877bc1 100644 --- a/scripts/base/frameworks/metrics/main.bro +++ b/scripts/base/frameworks/metrics/main.bro @@ -15,6 +15,10 @@ export { ## current value to the logging stream. const default_break_interval = 15mins &redef; + ## This is the interval for how often notices will happen after they have + ## already fired. + const renotice_interval = 1hr &redef; + type Index: record { ## Host is the value to which this metric applies. host: addr &optional; @@ -56,7 +60,7 @@ export { pred: function(index: Index): bool &optional; ## Global mask by which you'd like to aggregate traffic. aggregation_mask: count &optional; - ## This is essentially applying names to various subnets. + ## This is essentially a mapping table between addresses and subnets. aggregation_table: table[subnet] of subnet &optional; ## The interval at which the metric should be "broken" and written ## to the logging stream. @@ -69,7 +73,6 @@ export { ## A straight threshold for generating a notice. notice_threshold: count &optional; ## A series of thresholds at which to generate notices. - ## TODO: This is not implemented yet! notice_thresholds: vector of count &optional; ## If this and a $notice_threshold value are set, this notice type ## will be generated by the metrics framework. @@ -78,10 +81,11 @@ export { global add_filter: function(id: ID, filter: Filter); global add_data: function(id: ID, index: Index, increment: count); + global index2str: function(index: Index): string; # This is the event that is used to "finish" metrics and adapt the metrics # framework for clustered or non-clustered usage. - global log_it: event(filter: Filter); + global log_it: event(filter: Filter); global log_metrics: event(rec: Info); } @@ -98,39 +102,58 @@ type MetricTable: table[Index] of count &default=0; global store: table[ID, string] of MetricTable = table(); # This stores the current threshold index for filters using the -# $notice_thresholds element. -global thresholds: table[string] of count = {} &default=0; +# $notice_threshold and $notice_thresholds elements. +global thresholds: table[ID, string, Index] of count = {} &create_expire=renotice_interval &default=0; event bro_init() &priority=5 { Log::create_stream(METRICS, [$columns=Info, $ev=log_metrics]); } +function index2str(index: Index): string + { + local out = ""; + if ( index?$host ) + out = fmt("%shost=%s", out, index$host); + if ( index?$network ) + out = fmt("%s%snetwork=%s", out, |out|==0 ? "" : ", ", index$network); + if ( index?$str ) + out = fmt("%s%sstr=%s", out, |out|==0 ? "" : ", ", index$str); + return fmt("metric_index(%s)", out); + } + function write_log(ts: time, filter: Filter, data: MetricTable) { for ( index in data ) { local val = data[index]; - local m: Info = [$ts=ts, + local m: Info = [$ts=ts, $metric_id=filter$id, $filter_name=filter$name, $index=index, $value=val]; - if ( m$index?$host && - filter?$notice_threshold && - m$value >= filter$notice_threshold ) + if ( (filter?$notice_threshold && + m$value >= filter$notice_threshold && + [filter$id, filter$name, index] !in thresholds) || + (filter?$notice_thresholds && + |filter$notice_thresholds| <= thresholds[filter$id, filter$name, index] && + m$value >= filter$notice_thresholds[thresholds[filter$id, filter$name, index]]) ) { - NOTICE([$note=filter$note, - $msg=fmt("Metrics threshold crossed by %s %d/%d", index$host, m$value, filter$notice_threshold), - $src=m$index$host, $n=m$value, - $metric_index=index]); - } - - else if ( filter?$notice_thresholds && - m$value >= filter$notice_thresholds[thresholds[cat(filter$id,filter$name)]] ) - { - # TODO: implement this + local n: Notice::Info = [$note=filter$note, $n=m$value, $metric_index=index]; + n$msg = fmt("Metrics threshold crossed by %s %d/%d", index2str(index), m$value, filter$notice_threshold); + if ( m$index?$str ) + n$sub = m$index$str; + if ( m$index?$host ) + n$src = m$index$host; + # TODO: not sure where to put the network yet. + + NOTICE(n); + + # This just needs set to some value so that it doesn't refire the + # notice until it expires from the table or it cross the next + # threshold in the case of vectors of thesholds. + ++thresholds[filter$id, filter$name, index]; } if ( filter$log ) @@ -193,7 +216,6 @@ function add_data(id: ID, index: Index, increment: count) ! filter$pred(index) ) next; - local filt_store = store[id, filter$name]; if ( index?$host ) { if ( filter?$aggregation_mask ) @@ -208,8 +230,9 @@ function add_data(id: ID, index: Index, increment: count) } } - if ( index !in filt_store ) - filt_store[index] = 0; - filt_store[index] += increment; + local metric_tbl = store[id, filter$name]; + if ( index !in metric_tbl ) + metric_tbl[index] = 0; + metric_tbl[index] += increment; } } diff --git a/scripts/base/frameworks/metrics/non-cluster.bro b/scripts/base/frameworks/metrics/non-cluster.bro index a96210649e..4e6d1e3d65 100644 --- a/scripts/base/frameworks/metrics/non-cluster.bro +++ b/scripts/base/frameworks/metrics/non-cluster.bro @@ -1,10 +1,6 @@ module Metrics; -export { - -} - event Metrics::log_it(filter: Filter) { local id = filter$id; diff --git a/scripts/policy/protocols/ssh/detect-bruteforcing.bro b/scripts/policy/protocols/ssh/detect-bruteforcing.bro index 36e73bfa59..e38f63ad8e 100644 --- a/scripts/policy/protocols/ssh/detect-bruteforcing.bro +++ b/scripts/policy/protocols/ssh/detect-bruteforcing.bro @@ -11,6 +11,12 @@ export { ## has now had a heuristically successful login attempt. Login_By_Password_Guesser, }; + + redef enum Metrics::ID += { + ## This metric is to measure failed logins with the hope of detecting + ## bruteforcing hosts. + FAILED_LOGIN, + }; ## The number of failed SSH connections before a host is designated as ## guessing passwords. @@ -35,45 +41,47 @@ export { global password_guessers: set[addr] &read_expire=guessing_timeout+1hr &synchronized; } +event bro_init() + { + Metrics::add_filter(FAILED_LOGIN, [$name="detect-bruteforcing", $log=F, + $note=Password_Guessing, + $notice_threshold=password_guesses_limit, + $break_interval=guessing_timeout]); + } + event SSH::heuristic_successful_login(c: connection) { local id = c$id; - # TODO: this should be migrated to the metrics framework. - if ( id$orig_h in password_rejections && - password_rejections[id$orig_h]$n > password_guesses_limit && - id$orig_h !in password_guessers ) - { - add password_guessers[id$orig_h]; - NOTICE([$note=Login_By_Password_Guesser, - $conn=c, - $n=password_rejections[id$orig_h]$n, - $msg=fmt("Successful SSH login by password guesser %s", id$orig_h), - $sub=fmt("%d failed logins", password_rejections[id$orig_h]$n)]); - } + # TODO: This is out for the moment pending some more additions to the + # metrics framework. + #if ( id$orig_h in password_guessers ) + # { + # NOTICE([$note=Login_By_Password_Guesser, + # $conn=c, + # $n=password_rejections[id$orig_h]$n, + # $msg=fmt("Successful SSH login by password guesser %s", id$orig_h), + # $sub=fmt("%d failed logins", password_rejections[id$orig_h]$n)]); + # } } event SSH::heuristic_failed_login(c: connection) { local id = c$id; - # presumed failure - if ( id$orig_h !in password_rejections ) - password_rejections[id$orig_h] = new_track_count(); - - # Track the number of rejections - # TODO: this should be migrated to the metrics framework. + # Add data to the FAILED_LOGIN metric unless this connection should + # be ignored. if ( ! (id$orig_h in ignore_guessers && id$resp_h in ignore_guessers[id$orig_h]) ) - ++password_rejections[id$orig_h]$n; + Metrics::add_data(FAILED_LOGIN, [$host=id$orig_h], 1); - if ( default_check_threshold(password_rejections[id$orig_h]) ) - { - add password_guessers[id$orig_h]; - NOTICE([$note=Password_Guessing, - $conn=c, - $msg=fmt("SSH password guessing by %s", id$orig_h), - $sub=fmt("%d apparently failed logins", password_rejections[id$orig_h]$n), - $n=password_rejections[id$orig_h]$n]); - } + #if ( default_check_threshold(password_rejections[id$orig_h]) ) + # { + # add password_guessers[id$orig_h]; + # NOTICE([$note=Password_Guessing, + # $conn=c, + # $msg=fmt("SSH password guessing by %s", id$orig_h), + # $sub=fmt("%d apparently failed logins", password_rejections[id$orig_h]$n), + # $n=password_rejections[id$orig_h]$n]); + # } } \ No newline at end of file diff --git a/testing/btest/Baseline/policy.frameworks.metrics.notice/notice.log b/testing/btest/Baseline/policy.frameworks.metrics.notice/notice.log index 112fe69e4b..282e3c7b7b 100644 --- a/testing/btest/Baseline/policy.frameworks.metrics.notice/notice.log +++ b/testing/btest/Baseline/policy.frameworks.metrics.notice/notice.log @@ -1,4 +1,4 @@ # ts uid id.orig_h id.orig_p id.resp_h id.resp_p note msg sub src dst p n peer_descr actions policy_items dropped remote_location.country_code remote_location.region remote_location.city remote_location.latitude remote_location.longitude metric_index.host metric_index.str metric_index.network -1313432466.662314 - - - - - Test_Notice Metrics threshold crossed by 6.5.4.3 2/1 - 6.5.4.3 - - 2 bro Notice::ACTION_LOG 4 - - - - - - 6.5.4.3 - - -1313432466.662314 - - - - - Test_Notice Metrics threshold crossed by 1.2.3.4 3/1 - 1.2.3.4 - - 3 bro Notice::ACTION_LOG 4 - - - - - - 1.2.3.4 - - -1313432466.662314 - - - - - Test_Notice Metrics threshold crossed by 7.2.1.5 1/1 - 7.2.1.5 - - 1 bro Notice::ACTION_LOG 4 - - - - - - 7.2.1.5 - - +1313508844.321207 - - - - - Test_Notice Metrics threshold crossed by metric_index(host=6.5.4.3) 2/1 - 6.5.4.3 - - 2 bro Notice::ACTION_LOG 4 - - - - - - 6.5.4.3 - - +1313508844.321207 - - - - - Test_Notice Metrics threshold crossed by metric_index(host=1.2.3.4) 3/1 - 1.2.3.4 - - 3 bro Notice::ACTION_LOG 4 - - - - - - 1.2.3.4 - - +1313508844.321207 - - - - - Test_Notice Metrics threshold crossed by metric_index(host=7.2.1.5) 1/1 - 7.2.1.5 - - 1 bro Notice::ACTION_LOG 4 - - - - - - 7.2.1.5 - -