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