zeek/policy/interconn.bro

318 lines
8.2 KiB
Text

# $Id: interconn.bro 3997 2007-02-23 00:31:19Z vern $
#
# interconn - generic detection of interactive connections.
@load port-name
@load demux
# The following must be defined for the event engine to generate
# interconn events.
redef interconn_min_interarrival = 0.01 sec;
redef interconn_max_interarrival = 2.0 sec;
redef interconn_max_keystroke_pkt_size = 20;
redef interconn_default_pkt_size = 512;
redef interconn_stat_period = 15.0 sec;
redef interconn_stat_backoff = 1.5;
const interconn_min_num_pkts = 10 &redef; # min num of pkts sent
const interconn_min_duration = 2.0 sec &redef; # min duration for the connection
const interconn_ssh_len_disabled = T &redef;
const interconn_min_ssh_pkts_ratio = 0.6 &redef;
const interconn_min_bytes = 10 &redef;
const interconn_min_7bit_ascii_ratio = 0.75 &redef;
const interconn_min_num_lines = 2 &redef;
const interconn_min_normal_line_ratio = 0.5 &redef;
# alpha: portion of interarrival times within range
# [interconn_min_interarrival, interconn_max_interarrival]
#
# alpha should be >= interconn_min_alpha
#
# gamma: num_keystrokes_two_in_row / num_pkts
# gamma indicates the portion of keystrokes in the overall traffic
#
# gamma should be >= interconn_min_gamma
const interconn_min_alpha = 0.2 &redef; # minimum required alpha
const interconn_min_gamma = 0.2 &redef; # minimum required gamma
const interconn_standard_ports = { telnet, rlogin, ftp, ssh, smtp, 143/tcp, 110/tcp } &redef;
const interconn_ignore_standard_ports = F &redef;
const interconn_demux_disabled = T &redef;
const INTERCONN_UNKNOWN = 0; # direction/interactivity is unknown
const INTERCONN_FORWARD = 1; # forward: a conn's orig is true originator
const INTERCONN_BACKWARD = 2; # backward: a conn's resp is true originator
const INTERCONN_INTERACTIVE = 1; # a conn is interactive
const INTERCONN_STANDARD_PORT = 2; # conn involves a standard port to ignore
type conn_info : record {
interactive: count; # interactivity: unknown/interactive/standard_port
dir: count; # direction: unknown/forward/backward
};
global interconn_conns: table [conn_id] of conn_info; # table for all connections
# Table for resp_endp's of those established (non-partial) conn's.
# If a partial conn connects to one of such resp's, we can infer
# its direction.
global interconn_resps: table [addr, port] of count &default = 0;
global interconn_log = open_log_file("interconn") &redef;
global num_interconns = 0;
function interconn_conn_string(c: connection): string
{
return fmt("%.6f %s.%d > %s.%d",
c$start_time,
c$id$orig_h, c$id$orig_p,
c$id$resp_h, c$id$resp_p);
}
function interconn_weird(c: connection, s: string)
{
print fmt("%s interconn_weird: %s %s", network_time(), interconn_conn_string(c), s);
}
function get_direction(c: connection): count
{
local id = c$id;
if ( interconn_conns[id]$dir != INTERCONN_UNKNOWN )
return interconn_conns[id]$dir;
# The connection is not established yet, but one endpoint
# is a known resp_endp
if ( [id$resp_h, id$resp_p] in interconn_resps )
{
interconn_conns[id]$dir = INTERCONN_FORWARD;
++interconn_resps[id$resp_h, id$resp_p];
return INTERCONN_FORWARD;
}
else if ( [id$orig_h, id$orig_p] in interconn_resps )
{
interconn_conns[id]$dir = INTERCONN_BACKWARD;
++interconn_resps[id$orig_h, id$orig_p];
return INTERCONN_BACKWARD;
}
return INTERCONN_UNKNOWN;
}
function comp_gamma(s: interconn_endp_stats): double
{
return s$num_pkts >= interconn_min_num_pkts ?
(1.0 * s$num_keystrokes_two_in_row) / s$num_pkts : 0.0;
}
function comp_alpha(s: interconn_endp_stats) : double
{
return ( s$num_keystrokes_two_in_row > 0 ) ?
(1.0 * s$num_normal_interarrivals / s$num_keystrokes_two_in_row) : 0.0;
}
function skip_further_interconn_processing(c: connection)
{
# This used to call skip_further_processing()
# (if active_connection(c$id) returned T). But that's
# clearly wrong *if* we're also doing additional analysis
# on the connection. So do nothing.
}
function log_interconn(c: connection, tag: string)
{
print interconn_log, fmt("%s %s", interconn_conn_string(c), tag);
local id = c$id;
if ( interconn_demux_disabled )
skip_further_interconn_processing(c);
else
demux_conn(id, tag, "orig", "resp");
}
function is_interactive_endp(s: interconn_endp_stats): bool
{
# Criteria 1: num_pkts >= interconn_min_num_pkts.
if ( s$num_pkts < interconn_min_num_pkts )
return F;
# Criteria 2: gamma >= interconn_min_gamma.
if ( comp_gamma(s) < interconn_min_gamma )
return F;
# Criteria 3: alpha >= interconn_min_alpha.
if ( comp_alpha(s) < interconn_min_alpha )
return F;
return T;
}
event connection_established(c: connection)
{
local id = c$id;
local dir = interconn_conns[id]$dir;
if ( dir == INTERCONN_FORWARD )
return;
if ( dir == INTERCONN_BACKWARD )
{
interconn_weird(c, "inconsistent direction");
return;
}
interconn_conns[id]$dir = INTERCONN_FORWARD;
++interconn_resps[id$resp_h, id$resp_p];
}
event new_connection(c: connection)
{
local id = c$id;
local info: conn_info;
info$dir = INTERCONN_UNKNOWN;
if ( interconn_ignore_standard_ports &&
(id$orig_p in interconn_standard_ports ||
id$resp_p in interconn_standard_ports) )
{
info$interactive = INTERCONN_STANDARD_PORT;
skip_further_interconn_processing(c);
}
else
info$interactive = INTERCONN_UNKNOWN;
interconn_conns[id] = info;
}
event interconn_remove_conn(c: connection)
{
local id = c$id;
if ( id !in interconn_conns )
# This can happen for weird connections such as those
# with an initial SYN+FIN packet.
return;
local dir = interconn_conns[id]$dir;
delete interconn_conns[id];
delete demuxed_conn[c$id];
if ( dir == INTERCONN_FORWARD )
{
if ( --interconn_resps[id$resp_h, id$resp_p] == 0 )
delete interconn_resps[id$resp_h, id$resp_p];
}
else if ( dir == INTERCONN_BACKWARD )
{
if ( --interconn_resps[id$orig_h, id$orig_p] == 0 )
delete interconn_resps[id$orig_h, id$orig_p];
}
}
event interconn_stats(c: connection,
os: interconn_endp_stats, rs: interconn_endp_stats)
{
local id = c$id;
if ( id !in interconn_conns )
return;
if ( interconn_conns[id]$interactive != INTERCONN_UNKNOWN )
return; # already classified
if ( c$duration < interconn_min_duration )
# forget about excessively short connections
return;
local dir = get_direction(c);
# Criteria:
#
# if ( dir == FORWARD )
# (os) is interactive
# else if ( dir == BACKWARD )
# (rs) is interactive
# else
# either (os) or (rs) is interactive
if ( dir == INTERCONN_FORWARD )
{
if ( ! is_interactive_endp(os) )
return;
}
else if ( dir == INTERCONN_BACKWARD )
{
if ( ! is_interactive_endp(rs) )
return;
}
else
{
if ( ! is_interactive_endp(os) && ! is_interactive_endp(rs) )
return;
}
local tag: string;
if ( ! interconn_ssh_len_disabled && (os$is_partial || rs$is_partial) )
{
local num_pkts = os$num_pkts + rs$num_pkts;
local num_8k0_pkts = os$num_8k0_pkts + rs$num_8k0_pkts;
local num_8k4_pkts = os$num_8k4_pkts + rs$num_8k4_pkts;
if ( num_8k0_pkts > num_pkts * interconn_min_ssh_pkts_ratio )
{
# c now considered as interactive.
interconn_conns[id]$interactive = INTERCONN_INTERACTIVE;
tag = fmt("interconn.%d.ssh2", ++num_interconns);
}
else if ( num_8k4_pkts > num_pkts * interconn_min_ssh_pkts_ratio )
{
# c now considered as interactive.
interconn_conns[id]$interactive = INTERCONN_INTERACTIVE;
tag = fmt("interconn.%d.ssh1", ++num_interconns);
}
}
# Criteria 4: num_7bit_ascii / num_bytes is big enough; AND
# enough number of normal lines
if ( interconn_conns[id]$interactive != INTERCONN_INTERACTIVE )
{
local num_bytes = os$num_bytes + rs$num_bytes;
local num_7bit_ascii = os$num_7bit_ascii + rs$num_7bit_ascii;
if ( num_bytes < interconn_min_bytes ||
num_7bit_ascii < num_bytes * interconn_min_7bit_ascii_ratio )
return;
local num_lines = os$num_lines + rs$num_lines;
local num_normal_lines = os$num_normal_lines +
rs$num_normal_lines;
if ( num_lines < interconn_min_num_lines ||
num_normal_lines < num_lines * interconn_min_normal_line_ratio )
return;
# c now considered as interactive.
interconn_conns[id]$interactive = INTERCONN_INTERACTIVE;
tag = fmt("interconn.%d", ++num_interconns);
}
log_interconn(c, tag);
}