# $Id: signatures.bro,v 1.14 2003/08/31 00:40:21 vern Exp $

@load alert

redef enum Alert += {
	SensitiveSignature,	# generic for triggering a log-worthy signature
	MultipleSignatures,	# host has triggered many signatures
	CountSignature,		# sig. has triggered mutliple times for a dest
};

type SigAction: enum {
	SIG_IGNORE,	# ignore this signature completely (even for scan detection)
	SIG_QUIET,	# process but don't report individually
	SIG_FILE,	# write into the signatures.log file
	SIG_LOG,	# log it and write into the signatures.log file
	SIG_LOG_NO_WORM,	# log if not originated by a known worm-source
	SIG_COUNT,	# Count per dest and log if threshold reached
};

# Actions for a signature.
const signature_actions: table[string] of SigAction =  {

	["sid-526"] = SIG_IGNORE,
		# sid-526 BAD TRAFFIC data in TCP SYN packet

} &redef &default = SIG_LOG;

global sig_file = open_log_file("signatures");

# Scan detection.

# Log if for a pair [orig, signature] the number of different responders has
# reached one of the thresholds.
const horiz_scan_thresholds : set[count] = { 5, 10, 50, 100, 500, 1000 } &redef;

# Log if for a pair [orig, resp] the number of different signature matches has
# reached one of the thresholds.
const vert_scan_thresholds : set[count] = { 5, 10, 50, 100, 500, 1000 } &redef;

# Log if a SIG_COUNT signature is triggered as often as given by one of
# these thresholds.
const count_thresholds : set[count] = {
	5, 10, 50, 100, 500, 1000, 10000, 1000000,
} &redef;

type sig_set: set[string];
type addr_set: set[addr];

# You may want to define some &read_expires on these:
global horiz_table: table[addr, string] of addr_set;
global vert_table: table[addr, addr] of sig_set;
global last_hthresh: table[addr] of count &default = 0;
global last_vthresh: table[addr] of count &default = 0;
global count_table: table[addr, string] of count &default = 0;

function log_scan_and_file(txt: string)
	{
	ALERT([$alert=MultipleSignatures, $msg=txt]);
	local msg = fmt("%.6f %s", network_time(), txt);
	print sig_file, msg;
	}

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);

	local esc = escape_string(data);

	if ( byte_len(esc) > 20 )
		esc = fmt( "%s...", sub_bytes(esc, 0, 20) );

	if ( action == SIG_FILE || action == SIG_LOG || action == SIG_LOG_NO_WORM )
		print sig_file, fmt("%f %s/%d > %s/%d %s %s [%s]", network_time(),
				state$conn$id$orig_h, state$conn$id$orig_p,
				state$conn$id$resp_h, state$conn$id$resp_p,
				state$id, msg, esc);

	local alert = F;

	if ( action == SIG_LOG )
		alert = T;

@ifdef ( is_worm_infectee )
	if ( action == SIG_LOG_NO_WORM && ! is_worm_infectee( state$conn$id$orig_h ) )
		alert = T;
@endif

	if ( action == SIG_COUNT )
		{
		local dst = state$conn$id$resp_h;
		if ( ++count_table[dst,id] in count_thresholds )
			{
			ALERT([$alert=CountSignature, $conn=state$conn,
				   $msg=fmt("%d matches of signature %s on host %s", count_table[dst,id],
							state$id, dst)]);
			}
		}

	if ( alert )
		{
		ALERT([$alert=SensitiveSignature, $conn=state$conn,
			$msg=fmt("%s > %s signature match for %s: %s [%s]",
				state$conn$id$orig_h, state$conn$id$resp_h,
				state$id, msg, esc)]);
		}

	# Keep track of scans
	local orig = state$conn$id$orig_h;
	local resp = state$conn$id$resp_h;

	if ( [orig, id] !in horiz_table )
		{
		local emptyset1: addr_set;
		horiz_table[orig, id] = emptyset1;
		}
	add horiz_table[orig, id][resp];

	if ( [orig, resp] !in vert_table )
		{
		local emptyset2: sig_set;
		vert_table[orig, resp] = emptyset2;
		}
	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] )
		{
		log_scan_and_file(fmt("%s has triggered signature %s on %d hosts",
				orig, id, hcount));
		last_hthresh[orig] = hcount;
		}

	if ( vcount in vert_scan_thresholds && vcount != last_vthresh[orig] )
		{
		log_scan_and_file(fmt("%s has triggered %d different signatures on host %s",
				orig, vcount, resp));
		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;
	}
