mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
318 lines
8.2 KiB
Text
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);
|
|
}
|