mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00

While we support initializing records via coercion from an expression list, e.g., local x: X = [$x1=1, $x2=2]; this can sometimes obscure the code to readers, e.g., when assigning to value declared and typed elsewhere. The language runtime has a similar overhead since instead of just constructing a known type it needs to check at runtime that the coercion from the expression list is valid; this can be slower than just writing the readible code in the first place, see #4559. With this patch we use explicit construction, e.g., local x = X($x1=1, $x2=2);
306 lines
10 KiB
Text
306 lines
10 KiB
Text
##! Script level signature support. See the
|
|
##! :doc:`signature documentation </frameworks/signatures>` for more
|
|
##! information about Zeek's signature engine.
|
|
|
|
@load base/frameworks/notice
|
|
|
|
module Signatures;
|
|
|
|
export {
|
|
## Add various signature-related notice types.
|
|
redef enum Notice::Type += {
|
|
## Generic notice type for notice-worthy signature matches.
|
|
Sensitive_Signature,
|
|
## Host has triggered many signatures on the same host. The
|
|
## number of signatures is defined by the
|
|
## :zeek:id:`Signatures::vert_scan_thresholds` variable.
|
|
Multiple_Signatures,
|
|
## Host has triggered the same signature on multiple hosts as
|
|
## defined by the :zeek:id:`Signatures::horiz_scan_thresholds`
|
|
## variable.
|
|
Multiple_Sig_Responders,
|
|
## The same signature has triggered multiple times for a host.
|
|
## The number of times the signature has been triggered is
|
|
## defined by the :zeek:id:`Signatures::count_thresholds`
|
|
## variable. To generate this notice, the
|
|
## :zeek:enum:`Signatures::SIG_COUNT_PER_RESP` action must be
|
|
## set for the signature.
|
|
Count_Signature,
|
|
## Summarize the number of times a host triggered a signature.
|
|
## The interval between summaries is defined by the
|
|
## :zeek:id:`Signatures::summary_interval` variable.
|
|
Signature_Summary,
|
|
};
|
|
|
|
## The signature logging stream identifier.
|
|
redef enum Log::ID += { LOG };
|
|
|
|
## A default logging policy hook for the stream.
|
|
global log_policy: Log::PolicyHook;
|
|
|
|
## These are the default actions you can apply to signature matches.
|
|
## All of them write the signature record to the logging stream unless
|
|
## declared otherwise.
|
|
type Action: enum {
|
|
## Ignore this signature completely (even for scan detection).
|
|
## Don't write to the signatures logging stream.
|
|
SIG_IGNORE,
|
|
## Process through the various aggregate techniques, but don't
|
|
## report individually and don't write to the signatures logging
|
|
## stream.
|
|
SIG_QUIET,
|
|
## Generate a notice.
|
|
SIG_LOG,
|
|
## The same as :zeek:enum:`Signatures::SIG_LOG`, but ignore for
|
|
## aggregate/scan processing.
|
|
SIG_FILE_BUT_NO_SCAN,
|
|
## Generate a notice and set it to be alarmed upon.
|
|
SIG_ALARM,
|
|
## Alarm once per originator.
|
|
SIG_ALARM_PER_ORIG,
|
|
## Alarm once and then never again.
|
|
SIG_ALARM_ONCE,
|
|
## Count signatures per responder host and alarm with the
|
|
## :zeek:enum:`Signatures::Count_Signature` notice if a threshold
|
|
## defined by :zeek:id:`Signatures::count_thresholds` is reached.
|
|
SIG_COUNT_PER_RESP,
|
|
## Don't alarm, but generate per-orig summary.
|
|
SIG_SUMMARY,
|
|
};
|
|
|
|
## The record type which contains the column fields of the signature log.
|
|
type Info: record {
|
|
## The network time at which a signature matching type of event
|
|
## to be logged has occurred.
|
|
ts: time &log;
|
|
## A unique identifier of the connection which triggered the
|
|
## signature match event.
|
|
uid: string &log &optional;
|
|
## The host which triggered the signature match event.
|
|
src_addr: addr &log &optional;
|
|
## The host port on which the signature-matching activity
|
|
## occurred.
|
|
src_port: port &log &optional;
|
|
## The destination host which was sent the payload that
|
|
## triggered the signature match.
|
|
dst_addr: addr &log &optional;
|
|
## The destination host port which was sent the payload that
|
|
## triggered the signature match.
|
|
dst_port: port &log &optional;
|
|
## Notice associated with signature event.
|
|
note: Notice::Type &log;
|
|
## The name of the signature that matched.
|
|
sig_id: string &log &optional;
|
|
## A more descriptive message of the signature-matching event.
|
|
event_msg: string &log &optional;
|
|
## Extracted payload data or extra message.
|
|
sub_msg: string &log &optional;
|
|
## Number of sigs, usually from summary count.
|
|
sig_count: count &log &optional;
|
|
## Number of hosts, from a summary count.
|
|
host_count: count &log &optional;
|
|
};
|
|
|
|
## Actions for a signature. Can be updated dynamically.
|
|
global actions: table[string] of Action = {
|
|
["unspecified"] = SIG_IGNORE, # place-holder
|
|
} &redef &default = SIG_ALARM;
|
|
|
|
## Signature IDs that should always be ignored.
|
|
option ignored_ids = /NO_DEFAULT_MATCHES/;
|
|
|
|
## Generate a notice 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;
|
|
|
|
## Generate a notice 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;
|
|
|
|
## Generate a notice if a :zeek:enum:`Signatures::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;
|
|
|
|
## The interval between when :zeek:enum:`Signatures::Signature_Summary`
|
|
## notices are generated.
|
|
option summary_interval = 1 day;
|
|
|
|
## This event can be handled to access/alter data about to be logged
|
|
## to the signature logging stream.
|
|
##
|
|
## rec: The record of signature data about to be logged.
|
|
global log_signature: event(rec: Info);
|
|
}
|
|
|
|
global horiz_table: table[addr, string] of addr_set &read_expire = 1 hr;
|
|
global vert_table: table[addr, addr] of string_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 zeek_init() &priority=5
|
|
{
|
|
Log::create_stream(Signatures::LOG, Log::Stream($columns=Info, $ev=log_signature, $path="signatures", $policy=log_policy));
|
|
}
|
|
|
|
event sig_summary(orig: addr, id: string, msg: string)
|
|
{
|
|
NOTICE(Notice::Info($note=Signature_Summary, $src=orig,
|
|
$msg=fmt("%s: %s", orig, msg),
|
|
$n=count_per_orig[orig,id]));
|
|
}
|
|
|
|
event signature_match(state: signature_state, msg: string, data: string)
|
|
{
|
|
local sig_id = state$sig_id;
|
|
local action = actions[sig_id];
|
|
|
|
if ( action == SIG_IGNORE || ignored_ids in sig_id )
|
|
return;
|
|
|
|
# Trim the matched data down to something reasonable
|
|
if ( |data| > 140 )
|
|
data = fmt("%s...", sub_bytes(data, 0, 140));
|
|
|
|
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;
|
|
}
|
|
|
|
if ( action != SIG_QUIET && action != SIG_COUNT_PER_RESP )
|
|
{
|
|
local info = Info($ts=network_time(),
|
|
$note=Sensitive_Signature,
|
|
$uid=state$conn$uid,
|
|
$src_addr=src_addr,
|
|
$src_port=src_port,
|
|
$dst_addr=dst_addr,
|
|
$dst_port=dst_port,
|
|
$event_msg=fmt("%s: %s", src_addr, msg),
|
|
$sig_id=sig_id,
|
|
$sub_msg=data);
|
|
Log::write(Signatures::LOG, info);
|
|
}
|
|
|
|
local notice = F;
|
|
if ( action == SIG_ALARM )
|
|
notice = T;
|
|
|
|
if ( action == SIG_COUNT_PER_RESP )
|
|
{
|
|
local dst = state$conn$id$resp_h;
|
|
if ( ++count_per_resp[dst,sig_id] in count_thresholds )
|
|
{
|
|
NOTICE(Notice::Info($note=Count_Signature, $conn=state$conn,
|
|
$msg=msg,
|
|
$n=count_per_resp[dst,sig_id],
|
|
$sub=fmt("%d matches of signature %s on host %s",
|
|
count_per_resp[dst,sig_id],
|
|
sig_id, dst)));
|
|
}
|
|
}
|
|
|
|
if ( (action == SIG_ALARM_PER_ORIG || action == SIG_SUMMARY) &&
|
|
++count_per_orig[state$conn$id$orig_h, sig_id] == 1 )
|
|
{
|
|
if ( action == SIG_ALARM_PER_ORIG )
|
|
notice = T;
|
|
else
|
|
schedule summary_interval {
|
|
sig_summary(state$conn$id$orig_h, sig_id, msg)
|
|
};
|
|
}
|
|
|
|
if ( action == SIG_ALARM_ONCE )
|
|
{
|
|
if ( [sig_id] !in did_sig_log )
|
|
{
|
|
notice = T;
|
|
add did_sig_log[sig_id];
|
|
}
|
|
}
|
|
|
|
if ( notice )
|
|
NOTICE(Notice::Info($note=Sensitive_Signature,
|
|
$conn=state$conn, $src=src_addr,
|
|
$dst=dst_addr, $msg=fmt("%s: %s", src_addr, msg),
|
|
$sub=data));
|
|
|
|
if ( action == SIG_FILE_BUT_NO_SCAN || action == SIG_SUMMARY )
|
|
return;
|
|
|
|
# Keep track of scans.
|
|
local orig = state$conn$id$orig_h;
|
|
local resp = state$conn$id$resp_h;
|
|
|
|
if ( [orig, sig_id] !in horiz_table )
|
|
horiz_table[orig, sig_id] = set();
|
|
|
|
add horiz_table[orig, sig_id][resp];
|
|
|
|
if ( [orig, resp] !in vert_table )
|
|
vert_table[orig, resp] = set();
|
|
|
|
add vert_table[orig, resp][sig_id];
|
|
|
|
local hcount = |horiz_table[orig, sig_id]|;
|
|
local vcount = |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, sig_id, hcount);
|
|
|
|
Log::write(Signatures::LOG,
|
|
Info($ts=network_time(), $note=Multiple_Sig_Responders,
|
|
$src_addr=orig, $sig_id=sig_id, $event_msg=msg,
|
|
$host_count=hcount, $sub_msg=horz_scan_msg));
|
|
|
|
NOTICE(Notice::Info($note=Multiple_Sig_Responders, $src=orig,
|
|
$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);
|
|
|
|
Log::write(Signatures::LOG,
|
|
Info($ts=network_time(),
|
|
$note=Multiple_Signatures,
|
|
$src_addr=orig,
|
|
$dst_addr=resp, $sig_id=sig_id, $sig_count=vcount,
|
|
$event_msg=fmt("%s different signatures triggered", vcount),
|
|
$sub_msg=vert_scan_msg));
|
|
|
|
NOTICE(Notice::Info($note=Multiple_Signatures, $src=orig, $dst=resp,
|
|
$msg=fmt("%s different signatures triggered", vcount),
|
|
$n=vcount, $sub=vert_scan_msg));
|
|
|
|
last_vthresh[orig] = vcount;
|
|
}
|
|
}
|