zeek/policy/signatures.bro

307 lines
8.8 KiB
Text

# $Id: signatures.bro 4909 2007-09-24 02:26:36Z vern $
@load notice
redef enum Notice += {
SensitiveSignature, # generic for alarm-worthy
MultipleSignatures, # host has triggered many signatures
MultipleSigResponders, # host has triggered same signature on
# multiple responders
CountSignature, # sig. has triggered mutliple times for a dest
SignatureSummary, # summarize # times a host triggered a signature
};
type SigAction: enum {
SIG_IGNORE, # ignore this sig. completely (even for scan detection)
SIG_QUIET, # process, but don't report individually
SIG_FILE, # write to signatures and notice files
SIG_FILE_BUT_NO_SCAN, # as SIG_FILE, but ignore for scan processing
SIG_ALARM, # alarm and write to signatures, notice, and alarm files
SIG_ALARM_PER_ORIG, # alarm once per originator
SIG_ALARM_ONCE, # alarm once and then never again
SIG_ALARM_NO_WORM, # alarm if not originated by a known worm-source
SIG_COUNT_PER_RESP, # count per dest. and alarm if threshold reached
SIG_SUMMARY, # don't alarm, but generate per-orig summary
};
# Actions for a signature.
const signature_actions: table[string] of SigAction = {
["unspecified"] = SIG_IGNORE, # place-holder
} &redef &default = SIG_ALARM;
type sig_info: record {
note: Notice; # notice associated with signature event
src_addr: addr &optional;
src_port: port &optional;
dst_addr: addr &optional;
dst_port: port &optional;
sig_id: string &optional &default="";
event_msg: string;
sub_msg: string &optional; # matched payload data or extra message
sig_count: count &optional; # num. sigs, usually from summary count
host_count: count &optional; # num. hosts, from a summary count
};
global sig_file = open_log_file("signatures");
global sig_summary_interval = 1 day &redef;
# Given a string, returns an escaped version suitable for being
# printed in the colon-separated notice format. This means that
# (1) any colons are escaped using '\', and (2) any '\'s are
# likewise escaped.
function signature_escape(s: string): string
{
s = subst_string(s, "\\", "\\\\");
return subst_string(s, ":", "\\:");
}
# function call for writing to the signatures log file
function signature_file_write(s: sig_info)
{
local t = fmt("%.06f", network_time());
local src_addr = s?$src_addr ? fmt("%s", s$src_addr) : "";
local src_port = s?$src_port ? fmt("%s", s$src_port) : "";
local dst_addr = s?$dst_addr ? fmt("%s", s$dst_addr) : "";
local dst_port = s?$dst_port ? fmt("%s", s$dst_port) : "";
local sub_msg = s?$sub_msg ? signature_escape(s$sub_msg) : "";
local sig_count = s?$sig_count ? fmt("%s", s$sig_count) : "";
local host_count = s?$host_count ? fmt("%s", s$host_count) : "";
local info =
fmt("%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s",
t, s$note, src_addr, src_port, dst_addr,
dst_port, s$sig_id, s$event_msg, sub_msg,
sig_count, host_count);
print sig_file, info;
}
# Scan detection.
# Alarm if, for a pair [orig, signature], the number of different responders
# has reached one of the thresholds.
const horiz_scan_thresholds = { 5, 10, 50, 100, 500, 1000 } &redef;
# Alarm if, for a pair [orig, resp], the number of different signature matches
# has reached one of the thresholds.
const vert_scan_thresholds = { 5, 10, 50, 100, 500, 1000 } &redef;
# Alarm if a SIG_COUNT_PER_RESP signature is triggered as often as given
# by one of these thresholds.
const count_thresholds = { 5, 10, 50, 100, 500, 1000, 10000, 1000000, } &redef;
type sig_set: set[string];
type addr_set: set[addr];
# We may need to define some &read_expires on these:
global horiz_table: table[addr, string] of addr_set &read_expire = 1 hr;
global vert_table: table[addr, addr] of sig_set &read_expire = 1 hr;
global last_hthresh: table[addr] of count &default = 0 &read_expire = 1 hr;
global last_vthresh: table[addr] of count &default = 0 &read_expire = 1 hr;
global count_per_resp: table[addr, string] of count
&default = 0 &read_expire = 1 hr;
global count_per_orig: table[addr, string] of count
&default = 0 &read_expire = 1 hr;
global did_sig_log: set[string] &read_expire = 1 hr;
event sig_summary(orig: addr, id: string, msg: string)
{
@ifdef ( is_worm_infectee )
if ( is_worm_infectee(orig) )
return;
@endif
NOTICE([$note=SignatureSummary, $src=orig,
$filename=id, $msg=fmt("%s: %s", orig, msg),
$n=count_per_orig[orig,id] ]);
}
event signature_match(state: signature_state, msg: string, data: string)
{
local id = state$id;
local action = signature_actions[id];
if ( action == SIG_IGNORE )
return;
# We always add it to the connection record.
append_addl(state$conn, state$id);
# Trim the matched data down to something reasonable
if ( byte_len(data) > 140 )
data = fmt("%s...", sub_bytes(data, 0, 140));
if ( action != SIG_QUIET && action != SIG_COUNT_PER_RESP )
{
if ( state$is_orig )
{
signature_file_write(
[$note=SensitiveSignature,
$src_addr=state$conn$id$orig_h,
$src_port=state$conn$id$orig_p,
$dst_addr=state$conn$id$resp_h,
$dst_port=state$conn$id$resp_p,
$sig_id=state$id,
$event_msg=fmt("%s: %s", state$conn$id$orig_h, msg),
$sub_msg=data]);
}
else
{
signature_file_write(
[$note=SensitiveSignature,
$src_addr=state$conn$id$resp_h,
$src_port=state$conn$id$resp_p,
$dst_addr=state$conn$id$orig_h,
$dst_port=state$conn$id$orig_p,
$sig_id=state$id,
$event_msg=fmt("%s: %s", state$conn$id$resp_h, msg),
$sub_msg=data]);
}
}
local notice = F;
if ( action == SIG_ALARM )
notice = T;
@ifdef ( is_worm_infectee )
if ( action == SIG_ALARM_NO_WORM &&
! is_worm_infectee(state$conn$id$orig_h) )
notice = T;
@endif
if ( action == SIG_COUNT_PER_RESP )
{
local dst = state$conn$id$resp_h;
if ( ++count_per_resp[dst,id] in count_thresholds )
{
NOTICE([$note=CountSignature, $conn=state$conn,
$msg=msg,
$filename=id,
$n=count_per_resp[dst,id],
$sub=fmt("%d matches of signature %s on host %s",
count_per_resp[dst,id],
state$id, dst)]);
}
}
if ( (action == SIG_ALARM_PER_ORIG || action == SIG_SUMMARY) &&
++count_per_orig[state$conn$id$orig_h, state$id] == 1 )
{
if ( action == SIG_ALARM_PER_ORIG )
notice = T;
else
schedule sig_summary_interval
{
sig_summary(state$conn$id$orig_h, state$id, msg)
};
}
if ( action == SIG_ALARM_ONCE )
{
if ( [state$id] !in did_sig_log )
{
notice = T;
add did_sig_log[state$id];
}
}
if ( notice )
{
local src_addr: addr;
local src_port: port;
local dst_addr: addr;
local dst_port: port;
if ( state$is_orig )
{
src_addr = state$conn$id$orig_h;
src_port = state$conn$id$orig_p;
dst_addr = state$conn$id$resp_h;
dst_port = state$conn$id$resp_p;
}
else
{
src_addr = state$conn$id$resp_h;
src_port = state$conn$id$resp_p;
dst_addr = state$conn$id$orig_h;
dst_port = state$conn$id$orig_p;
}
NOTICE([$note=SensitiveSignature,
$conn=state$conn, $src=src_addr,
$dst=dst_addr, $filename=id, $msg=fmt("%s: %s", src_addr, msg),
$sub=data]);
}
if ( action == SIG_FILE_BUT_NO_SCAN || action == SIG_SUMMARY )
return;
@ifdef ( is_worm_infectee )
# Ignore scanning of known worm infectees.
if ( is_worm_infectee(state$conn$id$orig_h) )
return;
@endif
# Keep track of scans.
local orig = state$conn$id$orig_h;
local resp = state$conn$id$resp_h;
if ( [orig, id] !in horiz_table )
horiz_table[orig, id] = set();
add horiz_table[orig, id][resp];
if ( [orig, resp] !in vert_table )
vert_table[orig, resp] = set();
add vert_table[orig, resp][id];
local hcount = length(horiz_table[orig, id]);
local vcount = length(vert_table[orig, resp]);
if ( hcount in horiz_scan_thresholds && hcount != last_hthresh[orig] )
{
local horz_scan_msg =
fmt("%s has triggered signature %s on %d hosts",
orig, id, hcount);
signature_file_write([$note=MultipleSigResponders,
$src_addr=orig, $sig_id=id, $event_msg=msg,
$host_count=hcount, $sub_msg=horz_scan_msg]);
NOTICE([$note=MultipleSigResponders, $src=orig, $filename=id,
$msg=msg, $n=hcount, $sub=horz_scan_msg]);
last_hthresh[orig] = hcount;
}
if ( vcount in vert_scan_thresholds && vcount != last_vthresh[orig] )
{
local vert_scan_msg =
fmt("%s has triggered %d different signatures on host %s",
orig, vcount, resp);
signature_file_write([$note=MultipleSignatures, $src_addr=orig,
$dst_addr=resp, $sig_id=id, $sig_count=vcount,
$event_msg= fmt("%s different signatures triggered",
vcount),
$sub_msg=vert_scan_msg]);
NOTICE([$note=MultipleSignatures, $src=orig, $dst=resp,
$filename=id,
$msg=fmt("%s different signatures triggered", vcount),
$n=vcount, $sub=vert_scan_msg]);
last_vthresh[orig] = vcount;
}
}
# Returns true if the given signature has already been triggered for the given
# [orig, resp] pair.
function has_signature_matched(id: string, orig: addr, resp: addr): bool
{
return [orig, resp] in vert_table ? id in vert_table[orig, resp] : F;
}