Measurement framework is ready for testing.

- New, expanded API.
 - Calculations moved into plugins.
 - Scripts using measurement framework ported.
 - Updated the script-land queue implementation to make it more generic.
 -
This commit is contained in:
Seth Hall 2013-04-01 17:04:15 -04:00
parent 93eca70e6b
commit b477d2b02d
11 changed files with 183 additions and 186 deletions

View file

@ -255,6 +255,11 @@ function reset(m: Measurement)
function create(m: Measurement) function create(m: Measurement)
{ {
if ( (m?$threshold || m?$threshold_series) && ! m?$threshold_val )
{
Reporter::error("Measurement given a threshold with no $threshold_val function");
}
if ( ! m?$id ) if ( ! m?$id )
m$id=unique_id(""); m$id=unique_id("");
local tmp: table[Key] of Thresholding = table(); local tmp: table[Key] of Thresholding = table();
@ -365,9 +370,6 @@ function threshold_crossed(m: Measurement, key: Key, result: Result)
if ( ! m?$threshold_crossed ) if ( ! m?$threshold_crossed )
return; return;
#if ( val?$sample_queue )
# val$samples = Queue::get_str_vector(val$sample_queue);
# Add in the extra ResultVals to make threshold_crossed callbacks easier to write. # Add in the extra ResultVals to make threshold_crossed callbacks easier to write.
if ( |m$reducers| != |result| ) if ( |m$reducers| != |result| )
{ {

View file

@ -1,3 +1,4 @@
@load base/utils/queue
module Measurement; module Measurement;
@ -29,7 +30,10 @@ hook add_to_reducer_hook(r: Reducer, val: double, data: DataPoint, rv: ResultVal
{ {
if ( ! rv?$sample_queue ) if ( ! rv?$sample_queue )
rv$sample_queue = Queue::init([$max_len=r$samples]); rv$sample_queue = Queue::init([$max_len=r$samples]);
Queue::push(rv$sample_queue, data$str); if ( ! rv?$samples )
rv$samples = vector();
Queue::put(rv$sample_queue, data);
Queue::get_vector(rv$sample_queue, rv$samples);
} }
} }

View file

@ -27,6 +27,12 @@ function sum_threshold(data_id: string): threshold_function
}; };
} }
hook init_resultval_hook(r: Reducer, rv: ResultVal)
{
if ( SUM in r$apply && ! rv?$sum )
rv$sum = 0;
}
hook add_to_reducer_hook(r: Reducer, val: double, data: DataPoint, rv: ResultVal) hook add_to_reducer_hook(r: Reducer, val: double, data: DataPoint, rv: ResultVal)
{ {
if ( SUM in r$apply ) if ( SUM in r$apply )

View file

@ -10,7 +10,7 @@ export {
redef record ResultVal += { redef record ResultVal += {
## If cardinality is being tracked, the number of unique ## If cardinality is being tracked, the number of unique
## items is tracked here. ## items is tracked here.
unique: count &optional; unique: count &default=0;
}; };
} }

View file

@ -1,4 +1,4 @@
##! A FIFO string queue. ##! A FIFO queue.
module Queue; module Queue;
@ -23,17 +23,17 @@ export {
## Push a string onto the top of a queue. ## Push a string onto the top of a queue.
## ##
## q: The queue to push the string into. ## q: The queue to put the value into.
## ##
## val: The string to push ## val: The value to insert into the queue.
global push: function(q: Queue, val: any); global put: function(q: Queue, val: any);
## Pop a string from the bottom of a queue. ## Pop a string from the bottom of a queue.
## ##
## q: The queue to pop the string from. ## q: The queue to get the string from.
## ##
## Returns: The string popped from the queue. ## Returns: The value gotten from the queue.
global pop: function(q: Queue): any; global get: function(q: Queue): any;
## Merge two queue's together. If any settings are applied ## Merge two queue's together. If any settings are applied
## to the queues, the settings from q1 are used for the new ## to the queues, the settings from q1 are used for the new
@ -53,23 +53,14 @@ export {
## Returns: The length of the queue. ## Returns: The length of the queue.
global len: function(q: Queue): count; global len: function(q: Queue): count;
## Get the contents of the queue as a string vector. ## Get the contents of the queue as a vector.
## ##
## q: The queue. ## q: The queue.
## ##
## Returns: A :bro:type:`vector of string` containing the ## ret: A vector containing the
## current contents of q. ## current contents of q as the type of ret.
global get_str_vector: function(q: Queue): vector of string; global get_vector: function(q: Queue, ret: vector of any);
## Get the contents of the queue as a count vector. Use care
## with this function. If the data put into the queue wasn't
## integers you will get conversion errors.
##
## q: The queue.
##
## Returns: A :bro:type:`vector of count` containing the
## current contents of q.
global get_cnt_vector: function(q: Queue): vector of count;
} }
redef record Queue += { redef record Queue += {
@ -96,15 +87,15 @@ function init(s: Settings): Queue
return q; return q;
} }
function push(q: Queue, val: any) function put(q: Queue, val: any)
{ {
if ( q$settings?$max_len && len(q) >= q$settings$max_len ) if ( q$settings?$max_len && len(q) >= q$settings$max_len )
pop(q); get(q);
q$vals[q$top] = val; q$vals[q$top] = val;
++q$top; ++q$top;
} }
function pop(q: Queue): any function get(q: Queue): any
{ {
local ret = q$vals[q$bottom]; local ret = q$vals[q$bottom];
delete q$vals[q$bottom]; delete q$vals[q$bottom];
@ -120,9 +111,9 @@ function merge(q1: Queue, q2: Queue): Queue
for ( ignored_val in q1$vals ) for ( ignored_val in q1$vals )
{ {
if ( i in q1$vals ) if ( i in q1$vals )
push(ret, q1$vals[i]); put(ret, q1$vals[i]);
if ( j in q2$vals ) if ( j in q2$vals )
push(ret, q2$vals[j]); put(ret, q2$vals[j]);
++i; ++i;
++j; ++j;
} }
@ -134,9 +125,8 @@ function len(q: Queue): count
return |q$vals|; return |q$vals|;
} }
function get_str_vector(q: Queue): vector of string function get_vector(q: Queue, ret: vector of any)
{ {
local ret: vector of string;
local i = q$bottom; local i = q$bottom;
local j = 0; local j = 0;
# Really dumb hack, this is only to provide # Really dumb hack, this is only to provide
@ -147,32 +137,7 @@ function get_str_vector(q: Queue): vector of string
if ( i >= q$top ) if ( i >= q$top )
break; break;
ret[j] = cat(q$vals[i]); ret[j] = q$vals[i];
++j; ++i; ++j; ++i;
} }
return ret;
} }
function get_cnt_vector(q: Queue): vector of count
{
local ret: vector of count;
local i = q$bottom;
local j = 0;
# Really dumb hack, this is only to provide
# the iteration for the correct number of
# values in q$vals.
for ( ignored_val in q$vals )
{
if ( i >= q$top )
break;
# TODO: this is terrible and should be replaced by
# a more generic version of the various
# functions to get vectors of values.
# (the way "any" works right now makes this impossible though)
ret[j] = to_count(cat(q$vals[i]));
++j; ++i;
}
return ret;
}

View file

@ -8,22 +8,28 @@ export {
redef enum Log::ID += { LOG }; redef enum Log::ID += { LOG };
type Info: record { type Info: record {
## Timestamp when the log line was finished and written.
ts: time &log; ts: time &log;
## Time interval that the log line covers.
ts_delta: interval &log;
## The name of the "app", like "facebook" or "netflix".
app: string &log; app: string &log;
## The number of unique local hosts using the app.
uniq_hosts: count &log; uniq_hosts: count &log;
## The number of hits to the app in total.
hits: count &log; hits: count &log;
## The total number of bytes received by users of the app.
bytes: count &log; bytes: count &log;
}; };
## The frequency of logging the stats collected by this script. ## The frequency of logging the stats collected by this script.
const break_interval = 1min &redef; const break_interval = 15mins &redef;
} }
redef record connection += { redef record connection += {
resp_hostname: string &optional; resp_hostname: string &optional;
}; };
event bro_init() &priority=3 event bro_init() &priority=3
{ {
Log::create_stream(AppMeasurement::LOG, [$columns=Info]); Log::create_stream(AppMeasurement::LOG, [$columns=Info]);
@ -32,10 +38,11 @@ event bro_init() &priority=3
local r2: Measurement::Reducer = [$stream="apps.hits", $apply=set(Measurement::UNIQUE)]; local r2: Measurement::Reducer = [$stream="apps.hits", $apply=set(Measurement::UNIQUE)];
Measurement::create([$epoch=break_interval, Measurement::create([$epoch=break_interval,
$reducers=set(r1, r2), $reducers=set(r1, r2),
$period_finished(data: Measurement::ResultTable) = $epoch_finished(data: Measurement::ResultTable) =
{ {
local l: Info; local l: Info;
l$ts = network_time(); l$ts = network_time();
l$ts_delta = break_interval;
for ( key in data ) for ( key in data )
{ {
local result = data[key]; local result = data[key];
@ -48,7 +55,7 @@ event bro_init() &priority=3
}]); }]);
} }
function do_metric(id: conn_id, hostname: string, size: count) function do_measurement(id: conn_id, hostname: string, size: count)
{ {
if ( /\.youtube\.com$/ in hostname && size > 512*1024 ) if ( /\.youtube\.com$/ in hostname && size > 512*1024 )
{ {
@ -92,11 +99,11 @@ event ssl_established(c: connection)
event connection_finished(c: connection) event connection_finished(c: connection)
{ {
if ( c?$resp_hostname ) if ( c?$resp_hostname )
do_metric(c$id, c$resp_hostname, c$resp$size); do_measurement(c$id, c$resp_hostname, c$resp$size);
} }
event HTTP::log_http(rec: HTTP::Info) event HTTP::log_http(rec: HTTP::Info)
{ {
if( rec?$host ) if( rec?$host )
do_metric(rec$id, rec$host, rec$response_body_len); do_measurement(rec$id, rec$host, rec$response_body_len);
} }

View file

@ -49,53 +49,45 @@ export {
global log_traceroute: event(rec: Traceroute::Info); global log_traceroute: event(rec: Traceroute::Info);
} }
# Track hosts that have sent low TTL packets and which hosts they
# sent them to.
global low_ttlers: set[addr, addr] = {} &create_expire=2min &synchronized;
function traceroute_detected(src: addr, dst: addr)
{
Log::write(LOG, [$ts=network_time(), $src=src, $dst=dst]);
NOTICE([$note=Traceroute::Detected,
$msg=fmt("%s seems to be running traceroute", src),
$src=src, $dst=dst,
$identifier=cat(src)]);
}
event bro_init() &priority=5 event bro_init() &priority=5
{ {
Log::create_stream(Traceroute::LOG, [$columns=Info, $ev=log_traceroute]); Log::create_stream(Traceroute::LOG, [$columns=Info, $ev=log_traceroute]);
Metrics::add_filter("traceroute.time_exceeded", local r1: Measurement::Reducer = [$stream="traceroute.time_exceeded", $apply=set(Measurement::UNIQUE)];
[$log=F, local r2: Measurement::Reducer = [$stream="traceroute.low_ttl_packet", $apply=set(Measurement::SUM)];
$every=icmp_time_exceeded_interval, Measurement::create([$epoch=icmp_time_exceeded_interval,
$measure=set(Metrics::UNIQUE), $reducers=set(r1, r2),
$threshold_val(key: Measurement::Key, result: Measurement::Result) =
{
# Give a threshold value of zero depending on if the host
# sends a low ttl packet.
if ( require_low_ttl_packets && result["traceroute.low_ttl_packet"]$sum == 0 )
return 0;
else
return result["traceroute.time_exceeded"]$unique;
},
$threshold=icmp_time_exceeded_threshold, $threshold=icmp_time_exceeded_threshold,
$threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal) = { $threshold_crossed(key: Measurement::Key, result: Measurement::Result) =
local parts = split1(index$str, /-/); {
local parts = split1(key$str, /-/);
local src = to_addr(parts[1]); local src = to_addr(parts[1]);
local dst = to_addr(parts[2]); local dst = to_addr(parts[2]);
if ( require_low_ttl_packets ) Log::write(LOG, [$ts=network_time(), $src=src, $dst=dst]);
{ NOTICE([$note=Traceroute::Detected,
when ( [src, dst] in low_ttlers ) $msg=fmt("%s seems to be running traceroute", src),
{ $src=src, $dst=dst,
traceroute_detected(src, dst); $identifier=cat(src)]);
} }]);
}
else
traceroute_detected(src, dst);
}]);
} }
# Low TTL packets are detected with a signature. # Low TTL packets are detected with a signature.
event signature_match(state: signature_state, msg: string, data: string) event signature_match(state: signature_state, msg: string, data: string)
{ {
if ( state$sig_id == /traceroute-detector.*/ ) if ( state$sig_id == /traceroute-detector.*/ )
add low_ttlers[state$conn$id$orig_h, state$conn$id$resp_h]; Measurement::add_data("traceroute.low_ttl_packet", [$str=cat(state$conn$id$orig_h,"-",state$conn$id$resp_h)], [$num=1]);
} }
event icmp_time_exceeded(c: connection, icmp: icmp_conn, code: count, context: icmp_context) event icmp_time_exceeded(c: connection, icmp: icmp_conn, code: count, context: icmp_context)
{ {
Metrics::add_data("traceroute.time_exceeded", [$str=cat(context$id$orig_h,"-",context$id$resp_h)], [$str=cat(c$id$orig_h)]); Measurement::add_data("traceroute.time_exceeded", [$str=cat(context$id$orig_h,"-",context$id$resp_h)], [$str=cat(c$id$orig_h)]);
} }

View file

@ -52,59 +52,64 @@ export {
} }
function check_addr_scan_threshold(index: Metrics::Index, val: Metrics::ResultVal): bool #function check_addr_scan_threshold(key: Measurement::Key, val: Measurement::Result): bool
{ # {
# We don't need to do this if no custom thresholds are defined. # # We don't need to do this if no custom thresholds are defined.
if ( |addr_scan_custom_thresholds| == 0 ) # if ( |addr_scan_custom_thresholds| == 0 )
return F; # return F;
#
local service = to_port(index$str); # local service = to_port(key$str);
return ( service in addr_scan_custom_thresholds && # return ( service in addr_scan_custom_thresholds &&
val$sum > addr_scan_custom_thresholds[service] ); # val$sum > addr_scan_custom_thresholds[service] );
} # }
function addr_scan_threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal)
{
local side = Site::is_local_addr(index$host) ? "local" : "remote";
local dur = duration_to_mins_secs(val$end-val$begin);
local message=fmt("%s scanned at least %d unique hosts on port %s in %s", index$host, val$unique, index$str, dur);
NOTICE([$note=Address_Scan,
$src=index$host,
$p=to_port(index$str),
$sub=side,
$msg=message,
$identifier=cat(index$host)]);
}
function port_scan_threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal)
{
local side = Site::is_local_addr(index$host) ? "local" : "remote";
local dur = duration_to_mins_secs(val$end-val$begin);
local message = fmt("%s scanned at least %d unique ports of host %s in %s", index$host, val$unique, index$str, dur);
NOTICE([$note=Port_Scan,
$src=index$host,
$dst=to_addr(index$str),
$sub=side,
$msg=message,
$identifier=cat(index$host)]);
}
event bro_init() &priority=5 event bro_init() &priority=5
{ {
# Note: addr scans are trcked similar to: table[src_ip, port] of set(dst); local r1: Measurement::Reducer = [$stream="scan.addr.fail", $apply=set(Measurement::UNIQUE)];
Metrics::add_filter("scan.addr.fail", [$every=addr_scan_interval, Measurement::create([$epoch=addr_scan_interval,
$measure=set(Metrics::UNIQUE), $reducers=set(r1),
$threshold_func=check_addr_scan_threshold, $threshold_val(key: Measurement::Key, result: Measurement::Result) =
$threshold=addr_scan_threshold, {
$threshold_crossed=addr_scan_threshold_crossed]); return double_to_count(result["scan.addr.fail"]$unique);
},
#$threshold_func=check_addr_scan_threshold,
$threshold=addr_scan_threshold,
$threshold_crossed(key: Measurement::Key, result: Measurement::Result) =
{
local r = result["scan.addr.fail"];
local side = Site::is_local_addr(key$host) ? "local" : "remote";
local dur = duration_to_mins_secs(r$end-r$begin);
local message=fmt("%s scanned at least %d unique hosts on port %s in %s", key$host, r$unique, key$str, dur);
NOTICE([$note=Address_Scan,
$src=key$host,
$p=to_port(key$str),
$sub=side,
$msg=message,
$identifier=cat(key$host)]);
}]);
# Note: port scans are tracked similar to: table[src_ip, dst_ip] of set(port); # Note: port scans are tracked similar to: table[src_ip, dst_ip] of set(port);
Metrics::add_filter("scan.port.fail", [$every=port_scan_interval, local r2: Measurement::Reducer = [$stream="scan.port.fail", $apply=set(Measurement::UNIQUE)];
$measure=set(Metrics::UNIQUE), Measurement::create([$epoch=port_scan_interval,
$threshold=port_scan_threshold, $reducers=set(r2),
$threshold_crossed=port_scan_threshold_crossed]); $threshold_val(key: Measurement::Key, result: Measurement::Result) =
{
return double_to_count(result["scan.port.fail"]$unique);
},
$threshold=port_scan_threshold,
$threshold_crossed(key: Measurement::Key, result: Measurement::Result) =
{
local r = result["scan.port.fail"];
local side = Site::is_local_addr(key$host) ? "local" : "remote";
local dur = duration_to_mins_secs(r$end-r$begin);
local message = fmt("%s scanned at least %d unique ports of host %s in %s", key$host, r$unique, key$str, dur);
NOTICE([$note=Port_Scan,
$src=key$host,
$dst=to_addr(key$str),
$sub=side,
$msg=message,
$identifier=cat(key$host)]);
}]);
} }
function add_metrics(id: conn_id, reverse: bool) function add_metrics(id: conn_id, reverse: bool)
@ -145,10 +150,10 @@ function add_metrics(id: conn_id, reverse: bool)
# return F; # return F;
if ( hook Scan::addr_scan_policy(scanner, victim, scanned_port) ) if ( hook Scan::addr_scan_policy(scanner, victim, scanned_port) )
Metrics::add_data("scan.addr.fail", [$host=scanner, $str=cat(scanned_port)], [$str=cat(victim)]); Measurement::add_data("scan.addr.fail", [$host=scanner, $str=cat(scanned_port)], [$str=cat(victim)]);
if ( hook Scan::port_scan_policy(scanner, victim, scanned_port) ) if ( hook Scan::port_scan_policy(scanner, victim, scanned_port) )
Metrics::add_data("scan.port.fail", [$host=scanner, $str=cat(victim)], [$str=cat(scanned_port)]); Measurement::add_data("scan.port.fail", [$host=scanner, $str=cat(victim)], [$str=cat(scanned_port)]);
} }
function is_failed_conn(c: connection): bool function is_failed_conn(c: connection): bool

View file

@ -27,9 +27,9 @@ event bro_init()
{ {
Metrics::add_filter("ftp.failed_auth", [$every=bruteforce_measurement_interval, Metrics::add_filter("ftp.failed_auth", [$every=bruteforce_measurement_interval,
$measure=set(Metrics::UNIQUE), $measure=set(Metrics::UNIQUE),
$threshold_val_func(val: Metrics::ResultVal) = { return val$num; }, $threshold_val_func(val: Metrics::Result) = { return val$num; },
$threshold=bruteforce_threshold, $threshold=bruteforce_threshold,
$threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal) = $threshold_crossed(index: Metrics::Index, val: Metrics::Result) =
{ {
local dur = duration_to_mins_secs(val$end-val$begin); local dur = duration_to_mins_secs(val$end-val$begin);
local plural = val$unique>1 ? "s" : ""; local plural = val$unique>1 ? "s" : "";

View file

@ -50,11 +50,11 @@ export {
| /\/\*![[:digit:]]{5}.*?\*\// &redef; | /\/\*![[:digit:]]{5}.*?\*\// &redef;
} }
function format_sqli_samples(samples: vector of string): string function format_sqli_samples(samples: vector of Measurement::DataPoint): string
{ {
local ret = "SQL Injection samples\n---------------------"; local ret = "SQL Injection samples\n---------------------";
for ( i in samples ) for ( i in samples )
ret += "\n" + samples[i]; ret += "\n" + samples[i]$str;
return ret; return ret;
} }
@ -63,31 +63,41 @@ event bro_init() &priority=3
# Add filters to the metrics so that the metrics framework knows how to # Add filters to the metrics so that the metrics framework knows how to
# determine when it looks like an actual attack and how to respond when # determine when it looks like an actual attack and how to respond when
# thresholds are crossed. # thresholds are crossed.
Metrics::add_filter("http.sqli.attacker", local r1: Measurement::Reducer = [$stream="http.sqli.attacker", $apply=set(Measurement::SUM), $samples=collect_SQLi_samples];
[$every=sqli_requests_interval, Measurement::create([$epoch=sqli_requests_interval,
$measure=set(Metrics::SUM), $reducers=set(r1),
$threshold_val(key: Measurement::Key, result: Measurement::Result) =
{
return double_to_count(result["http.sqli.attacker"]$sum);
},
$threshold=sqli_requests_threshold, $threshold=sqli_requests_threshold,
$samples=collect_SQLi_samples, $threshold_crossed(key: Measurement::Key, result: Measurement::Result) =
$threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal) = { {
local r = result["http.sqli.attacker"];
NOTICE([$note=SQL_Injection_Attacker, NOTICE([$note=SQL_Injection_Attacker,
$msg="An SQL injection attacker was discovered!", $msg="An SQL injection attacker was discovered!",
$email_body_sections=vector(format_sqli_samples(val$samples)), $email_body_sections=vector(format_sqli_samples(r$samples)),
$src=index$host, $src=key$host,
$identifier=cat(index$host)]); $identifier=cat(key$host)]);
}]); }]);
Metrics::add_filter("http.sqli.victim", local r2: Measurement::Reducer = [$stream="http.sqli.victim", $apply=set(Measurement::SUM), $samples=collect_SQLi_samples];
[$every=sqli_requests_interval, Measurement::create([$epoch=sqli_requests_interval,
$measure=set(Metrics::SUM), $reducers=set(r2),
$threshold_val(key: Measurement::Key, result: Measurement::Result) =
{
return double_to_count(result["http.sqli.victim"]$sum);
},
$threshold=sqli_requests_threshold, $threshold=sqli_requests_threshold,
$samples=collect_SQLi_samples, $threshold_crossed(key: Measurement::Key, result: Measurement::Result) =
$threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal) = { {
local r = result["http.sqli.victim"];
NOTICE([$note=SQL_Injection_Victim, NOTICE([$note=SQL_Injection_Victim,
$msg="An SQL injection victim was discovered!", $msg="An SQL injection victim was discovered!",
$email_body_sections=vector(format_sqli_samples(val$samples)), $email_body_sections=vector(format_sqli_samples(r$samples)),
$src=index$host, $src=key$host,
$identifier=cat(index$host)]); $identifier=cat(key$host)]);
}]); }]);
} }
event http_request(c: connection, method: string, original_URI: string, event http_request(c: connection, method: string, original_URI: string,
@ -97,7 +107,7 @@ event http_request(c: connection, method: string, original_URI: string,
{ {
add c$http$tags[URI_SQLI]; add c$http$tags[URI_SQLI];
Metrics::add_data("http.sqli.attacker", [$host=c$id$orig_h], [$str=original_URI]); Measurement::add_data("http.sqli.attacker", [$host=c$id$orig_h], [$str=original_URI]);
Metrics::add_data("http.sqli.victim", [$host=c$id$resp_h], [$str=original_URI]); Measurement::add_data("http.sqli.victim", [$host=c$id$resp_h], [$str=original_URI]);
} }
} }

View file

@ -42,21 +42,27 @@ export {
event bro_init() event bro_init()
{ {
Metrics::add_filter("ssh.login.failure", [$name="detect-bruteforcing", $log=F, local r1: Measurement::Reducer = [$stream="ssh.login.failure", $apply=set(Measurement::SUM)];
$every=guessing_timeout, Measurement::create([$epoch=guessing_timeout,
$measure=set(Metrics::SUM), $reducers=set(r1),
$threshold=password_guesses_limit, $threshold_val(key: Measurement::Key, result: Measurement::Result) =
$threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal) = { {
# Generate the notice. return double_to_count(result["ssh.login.failure"]$sum);
NOTICE([$note=Password_Guessing, },
$msg=fmt("%s appears to be guessing SSH passwords (seen in %.0f connections).", index$host, val$sum), $threshold=password_guesses_limit,
$src=index$host, $threshold_crossed(key: Measurement::Key, result: Measurement::Result) =
$identifier=cat(index$host)]); {
# Insert the guesser into the intel framework. local r = result["ssh.login.failure"];
Intel::insert([$host=index$host, # Generate the notice.
$meta=[$source="local", NOTICE([$note=Password_Guessing,
$desc=fmt("Bro observed %0.f apparently failed SSH connections.", val$sum)]]); $msg=fmt("%s appears to be guessing SSH passwords (seen in %d connections).", key$host, r$num),
}]); $src=key$host,
$identifier=cat(key$host)]);
# Insert the guesser into the intel framework.
Intel::insert([$host=key$host,
$meta=[$source="local",
$desc=fmt("Bro observed %d apparently failed SSH connections.", r$num)]]);
}]);
} }
event SSH::heuristic_successful_login(c: connection) event SSH::heuristic_successful_login(c: connection)
@ -76,5 +82,5 @@ event SSH::heuristic_failed_login(c: connection)
# be ignored. # be ignored.
if ( ! (id$orig_h in ignore_guessers && if ( ! (id$orig_h in ignore_guessers &&
id$resp_h in ignore_guessers[id$orig_h]) ) id$resp_h in ignore_guessers[id$orig_h]) )
Metrics::add_data("ssh.login.failure", [$host=id$orig_h], [$num=1]); Measurement::add_data("ssh.login.failure", [$host=id$orig_h], [$num=1]);
} }