New default notice action for emailing network admins.

- When ACTION_EMAIL_ADMIN is applied to a notice,
  the email addresses associated with the address
  are collected from the new local_admins table
  and the email is sent to all discovered email addresses.
- The site.bro script is now in the Site module.
- Some other small cleanup.
This commit is contained in:
Seth Hall 2011-06-25 00:57:02 -04:00
parent 09e242f98f
commit 71d6488637
7 changed files with 97 additions and 35 deletions

View file

@ -16,7 +16,7 @@ export {
## also used by the global function :bro:id:`NOTICE`. ## also used by the global function :bro:id:`NOTICE`.
NOTICE, NOTICE,
## This is the notice policy auditing log. It records what the current ## This is the notice policy auditing log. It records what the current
## notice policy is at Bro init time.. ## notice policy is at Bro init time.
NOTICE_POLICY, NOTICE_POLICY,
}; };
@ -38,11 +38,15 @@ export {
ACTION_FILE, ACTION_FILE,
## Indicates that the notice should be alarmed on. ## Indicates that the notice should be alarmed on.
ACTION_ALARM, ACTION_ALARM,
## Indicates that the notice should be sent to the configured notice ## Indicates that the notice should be sent to the email address(es)
## contact email address(es). ## configured in the :bro:id:`mail_dest` variable.
ACTION_EMAIL, ACTION_EMAIL,
## Indicates that the notice should be sent to the configured pager ## Indicate that the generated email should be addressed to the
## email address. ## appropriate addresses as found in the :bro:id:`Site::addr_to_emails`
## variable.
ACTION_EMAIL_ADMIN,
## Indicates that the notice should be sent to the pager email address
## configured in the :bro:id:`mail_page_dest` variable.
ACTION_PAGE, ACTION_PAGE,
## Indicates that no more actions should be found after the policy ## Indicates that no more actions should be found after the policy
## item returning this matched. ## item returning this matched.
@ -113,15 +117,15 @@ export {
# This is the :bro:id:`Notice::policy` where the local notice conversion # This is the :bro:id:`Notice::policy` where the local notice conversion
# policy is set. # policy is set.
const policy: set[Notice::PolicyItem] = { const policy: set[Notice::PolicyItem] = {
[$pred(n: Notice::Info) = { return T; },
$result = ACTION_FILE,
$priority = 0],
[$pred(n: Notice::Info) = { return (n$note in ignored_types); }, [$pred(n: Notice::Info) = { return (n$note in ignored_types); },
$result = ACTION_STOP, $result = ACTION_STOP,
$priority = 10], $priority = 10],
[$pred(n: Notice::Info) = { return (n$note in emailed_types); }, [$pred(n: Notice::Info) = { return (n$note in emailed_types); },
$result = ACTION_EMAIL, $result = ACTION_EMAIL,
$priority = 9], $priority = 9],
[$pred(n: Notice::Info) = { return T; },
$result = ACTION_FILE,
$priority = 0],
} &redef; } &redef;
## Local system mail program. ## Local system mail program.

View file

@ -37,8 +37,8 @@ export {
## If the connection is originated locally, this value will be T. If ## If the connection is originated locally, this value will be T. If
## it was originated remotely it will be F. In the case that the ## it was originated remotely it will be F. In the case that the
## :bro:id:`local_nets` variable is undefined, this field will be left ## :bro:id:`Site::local_nets` variable is undefined, this field will
## empty at all times. ## be left empty at all times.
local_orig: bool &log &optional; local_orig: bool &log &optional;
## Indicates the number of bytes missed in content gaps which is ## Indicates the number of bytes missed in content gaps which is
@ -154,8 +154,8 @@ function set_conn(c: connection, eoc: bool)
tmp$uid=c$uid; tmp$uid=c$uid;
tmp$id=id; tmp$id=id;
tmp$proto=get_port_transport_proto(id$resp_p); tmp$proto=get_port_transport_proto(id$resp_p);
if( |local_nets| > 0 ) if( |Site::local_nets| > 0 )
tmp$local_orig=is_local_addr(id$orig_h); tmp$local_orig=Site::is_local_addr(id$orig_h);
c$conn = tmp; c$conn = tmp;
} }

View file

@ -17,21 +17,21 @@ export {
redef enum Notice::Type += { redef enum Notice::Type += {
## Raised when a non-local name is found to be pointing at a local host. ## Raised when a non-local name is found to be pointing at a local host.
## This only works appropriately when all of your authoritative DNS ## This only works appropriately when all of your authoritative DNS
## servers are located in your :bro:id:`local_nets`. ## servers are located in your :bro:id:`Site::local_nets`.
DNS_ExternalName, DNS_ExternalName,
}; };
} }
event dns_A_reply(c: connection, msg: dns_msg, ans: dns_answer, a: addr) &priority=-3 event dns_A_reply(c: connection, msg: dns_msg, ans: dns_answer, a: addr) &priority=-3
{ {
if ( |local_zones| == 0 ) if ( |Site::local_zones| == 0 )
return; return;
# Check for responses from remote hosts that point at local hosts # Check for responses from remote hosts that point at local hosts
# but the name is not considered to be within a "local" zone. # but the name is not considered to be within a "local" zone.
if ( is_local_addr(a) && # referring to a local host if ( Site::is_local_addr(a) && # referring to a local host
!is_local_addr(c$id$resp_h) && # response from an external nameserver !Site::is_local_addr(c$id$resp_h) && # response from an external nameserver
!is_local_name(ans$query) ) # name isn't in a local zone. !Site::is_local_name(ans$query) ) # name isn't in a local zone.
{ {
NOTICE([$note=DNS_ExternalName, NOTICE([$note=DNS_ExternalName,
$msg=fmt("%s is pointing to a local host - %s.", ans$query, a), $msg=fmt("%s is pointing to a local host - %s.", ans$query, a),

View file

@ -339,7 +339,7 @@ event smtp_data(c: connection, is_orig: bool, data: string) &priority=3
local ip = to_addr(text_ip); local ip = to_addr(text_ip);
if ( ! addr_matches_host(ip, mail_path_capture) && if ( ! addr_matches_host(ip, mail_path_capture) &&
ip !in private_address_space ) ! Site::is_private_addr(ip) )
{ {
c$smtp$process_received_from = F; c$smtp$process_received_from = F;
} }

View file

@ -8,8 +8,8 @@ export {
redef enum Notice::Type += { redef enum Notice::Type += {
Login, Login,
PasswordGuessing, Password_Guessing,
LoginByPasswordGuesser, Login_By_Password_Guesser,
Login_From_Interesting_Hostname, Login_From_Interesting_Hostname,
Bytecount_Inconsistency, Bytecount_Inconsistency,
}; };
@ -88,12 +88,6 @@ event bro_init()
Log::create_stream(SSH, [$columns=Info, $ev=log_ssh]); Log::create_stream(SSH, [$columns=Info, $ev=log_ssh]);
} }
# TODO: move this elsewhere
function local_filter(rec: record { id: conn_id; } ): bool
{
return is_local_addr(rec$id$resp_h);
}
function set_session(c: connection) function set_session(c: connection)
{ {
if ( ! c?$ssh ) if ( ! c?$ssh )
@ -124,7 +118,7 @@ function check_ssh_connection(c: connection, done: bool)
return; return;
local status = "failure"; local status = "failure";
local direction = is_local_addr(c$id$orig_h) ? "to" : "from"; local direction = Site::is_local_addr(c$id$orig_h) ? "to" : "from";
local location: geo_location; local location: geo_location;
location = (direction == "to") ? lookup_location(c$id$resp_h) : lookup_location(c$id$orig_h); location = (direction == "to") ? lookup_location(c$id$resp_h) : lookup_location(c$id$orig_h);
@ -142,7 +136,7 @@ function check_ssh_connection(c: connection, done: bool)
if ( default_check_threshold(password_rejections[c$id$orig_h]) ) if ( default_check_threshold(password_rejections[c$id$orig_h]) )
{ {
add password_guessers[c$id$orig_h]; add password_guessers[c$id$orig_h];
NOTICE([$note=PasswordGuessing, NOTICE([$note=Password_Guessing,
$conn=c, $conn=c,
$msg=fmt("SSH password guessing by %s", c$id$orig_h), $msg=fmt("SSH password guessing by %s", c$id$orig_h),
$sub=fmt("%d failed logins", password_rejections[c$id$orig_h]$n), $sub=fmt("%d failed logins", password_rejections[c$id$orig_h]$n),
@ -163,7 +157,7 @@ function check_ssh_connection(c: connection, done: bool)
c$id$orig_h !in password_guessers ) c$id$orig_h !in password_guessers )
{ {
add password_guessers[c$id$orig_h]; add password_guessers[c$id$orig_h];
NOTICE([$note=LoginByPasswordGuesser, NOTICE([$note=Login_By_Password_Guesser,
$conn=c, $conn=c,
$n=password_rejections[c$id$orig_h]$n, $n=password_rejections[c$id$orig_h]$n,
$msg=fmt("Successful SSH login by password guesser %s", c$id$orig_h), $msg=fmt("Successful SSH login by password guesser %s", c$id$orig_h),
@ -180,7 +174,7 @@ function check_ssh_connection(c: connection, done: bool)
$sub=location$country_code]); $sub=location$country_code]);
# Check to see if this login came from an interesting hostname # Check to see if this login came from an interesting hostname
when( local hostname = lookup_addr(c$id$orig_h) ) when ( local hostname = lookup_addr(c$id$orig_h) )
{ {
if ( interesting_hostnames in hostname ) if ( interesting_hostnames in hostname )
{ {
@ -190,6 +184,12 @@ function check_ssh_connection(c: connection, done: bool)
$sub=hostname]); $sub=hostname]);
} }
} }
if ( location$country_code in watched_countries )
{
}
} }
else if ( c$resp$size >= 200000000 ) else if ( c$resp$size >= 200000000 )
{ {

View file

@ -2,7 +2,7 @@
##! and "neighbors", and servers running particular services. ##! and "neighbors", and servers running particular services.
@load utils/pattern @load utils/pattern
module GLOBAL; module Site;
export { export {
## Address space that is considered private and unrouted. ## Address space that is considered private and unrouted.
@ -19,6 +19,12 @@ export {
## Networks that are considered "neighbors". ## Networks that are considered "neighbors".
const neighbor_nets: set[subnet] &redef; const neighbor_nets: set[subnet] &redef;
## If local network administrators are known and they have responsibility
## for defined address space, then a mapping can be defined here between
## networks for which they have responsibility and a set of email
## addresses.
const local_admins: table[subnet] of set[string] = {} &redef;
## DNS zones that are considered "local". ## DNS zones that are considered "local".
const local_zones: set[string] &redef; const local_zones: set[string] &redef;
@ -33,6 +39,10 @@ export {
## Function that returns true if an address corresponds to one of ## Function that returns true if an address corresponds to one of
## the neighbor networks, false if not. ## the neighbor networks, false if not.
global is_neighbor_addr: function(a: addr): bool; global is_neighbor_addr: function(a: addr): bool;
## Function that returns true if an address corresponds to one of
## the private/unrouted networks, false if not.
global is_private_addr: function(a: addr): bool;
## Function that returns true if a host name is within a local ## Function that returns true if a host name is within a local
## DNS zone. ## DNS zone.
@ -42,6 +52,10 @@ export {
## DNS zone. ## DNS zone.
global is_neighbor_name: function(name: string): bool; global is_neighbor_name: function(name: string): bool;
## Function that returns a common separated list of email addresses
## that are considered administrators for the IP address provided as
## an argument.
global get_emails: function(a: addr): string;
} }
# Please ignore, this is an interally used variable. # Please ignore, this is an interally used variable.
@ -74,6 +88,48 @@ function is_neighbor_name(name: string): bool
return local_dns_neighbor_suffix_regex in name; return local_dns_neighbor_suffix_regex in name;
} }
# This is a hack for doing a for loop.
const one_to_32: vector of count = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32};
# TODO: make this work with IPv6
function find_all_emails(ip: addr): set[string]
{
if ( ip !in local_admins ) return set();
local output_values: set[string] = set();
local tmp_ip: addr;
local i: count;
local emails: string;
for ( i in one_to_32 )
{
tmp_ip = mask_addr(ip, one_to_32[i]);
for ( email in local_admins[tmp_ip] )
{
if ( email != "" )
add output_values[email];
}
}
return output_values;
}
function fmt_email_string(emails: set[string]): string
{
local output="";
for( email in emails )
{
if ( output == "" )
output = email;
else
output = fmt("%s, %s", output, email);
}
return output;
}
function get_emails(a: addr): string
{
return fmt_email_string(find_all_emails(a));
}
event bro_init() &priority=10 event bro_init() &priority=10
{ {
# Double backslashes are needed due to string parsing. # Double backslashes are needed due to string parsing.

View file

@ -1,11 +1,13 @@
@load site
type Direction: enum { INBOUND, OUTBOUND, BIDIRECTIONAL, NO_DIRECTION }; type Direction: enum { INBOUND, OUTBOUND, BIDIRECTIONAL, NO_DIRECTION };
function id_matches_direction(id: conn_id, d: Direction): bool function id_matches_direction(id: conn_id, d: Direction): bool
{ {
if ( d == NO_DIRECTION ) return F; if ( d == NO_DIRECTION ) return F;
return ( d == BIDIRECTIONAL || return ( d == BIDIRECTIONAL ||
(d == OUTBOUND && is_local_addr(id$orig_h)) || (d == OUTBOUND && Site::is_local_addr(id$orig_h)) ||
(d == INBOUND && is_local_addr(id$resp_h)) ); (d == INBOUND && Site::is_local_addr(id$resp_h)) );
} }
type Host: enum { LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS }; type Host: enum { LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS };
@ -14,6 +16,6 @@ function addr_matches_host(ip: addr, h: Host): bool
if ( h == NO_HOSTS ) return F; if ( h == NO_HOSTS ) return F;
return ( h == ALL_HOSTS || return ( h == ALL_HOSTS ||
(h == LOCAL_HOSTS && is_local_addr(ip)) || (h == LOCAL_HOSTS && Site::is_local_addr(ip)) ||
(h == REMOTE_HOSTS && !is_local_addr(ip)) ); (h == REMOTE_HOSTS && !Site::is_local_addr(ip)) );
} }