# $Id: hot.bro 5096 2007-11-29 06:59:56Z vern $

@load site
@load port-name
@load notice
@load terminate-connection

module Hot;

export {
	redef enum Notice += {
		SSH_Overflow,	# volume signature of SSH overflow attack seen
	};

	# True if it should be considered a spoofing attack if a connection has
	# the same local net for source and destination.
	const same_local_net_is_spoof = F &redef;

	const allow_spoof_services = {
		110/tcp,	# pop-3
		139/tcp,	# netbios-ssn
	} &redef;

	# Indexed by source address and destination address.
	const allow_pairs: set[addr, addr] &redef;

	const hot_srcs: table[addr] of string = {
		#	[ph33r.the.eleet.com] = "kidz",
	} &redef;

	const hot_dsts: table[addr] of string = {
		[206.101.197.226] = "ILOVEYOU worm destination",
	} &redef;

	const allow_services = {
		ssh, http, gopher, ident, smtp, 20/tcp,
		53/udp,		# DNS queries
		123/udp,	# NTP
	} &redef;

	const allow_services_to: set[addr, port] &redef;
	const allow_services_from: set[addr, port] &redef;
	const allow_service_pairs: set[addr, addr, port] &redef;

	const flag_successful_service: table[port] of string = {
		[[31337/tcp]] = "popular backdoors",
		[[2766/tcp]] = "Solaris listen service",
	} &redef;

	const flag_successful_inbound_service: table[port] of string = {
		[1524/tcp] = "popular backdoor, but with false hits outbound",
	} &redef;

	const terminate_successful_inbound_service: table[port] of string &redef;

	const flag_rejected_service: table[port] of string &redef;

	# Different values to hand to check_hot() at different stages in
	# a connection's lifetime.
	const CONN_ATTEMPTED = 1;
	const CONN_ESTABLISHED = 2;
	const APPL_ESTABLISHED = 3;
	const CONN_FINISHED = 4;
	const CONN_REJECTED = 5;
	const CONN_TIMEOUT = 6;
	const CONN_REUSED = 7;

	global check_hot: function(c: connection, state: count): bool;
	global check_spoof: function(c: connection): bool;
}

# An internal function used by check_hot.
function do_hot_check(c: connection, a: addr, t: table[addr] of string)
	{
	if ( a in t )
		{
		++c$hot;
		local hot_msg = fmt("<%s>", t[a]);
		append_addl(c, hot_msg);
		}
	}

function check_spoof(c: connection): bool
	{
	local orig = c$id$orig_h;
	local resp = c$id$resp_h;
	local service = c$id$resp_p;

	if ( is_local_addr(orig) && is_local_addr(resp) &&
	     service !in allow_spoof_services )
		{
		if ( c$id$orig_p == service && orig == resp )
			event conn_weird("Land_attack", c);

		if ( same_local_net_is_spoof )
			++c$hot;
		}

	return c$hot != 0;
	}

function check_hot(c: connection, state: count): bool
	{
	local id = c$id;
	local service = id$resp_p;

	if ( service in allow_services || "ftp-data" in c$service )
		return F;

	if ( state == CONN_ATTEMPTED )
		check_spoof(c);

	else if ( state == CONN_REJECTED )
		{
		check_spoof(c);

		if ( service in flag_rejected_service )
			++c$hot;
		}

	else if ( state == CONN_ESTABLISHED )
		{
		check_spoof(c);

		local inbound = is_local_addr(id$resp_h);

		if ( (service in flag_successful_service ||
		      (inbound &&
		       service in flag_successful_inbound_service)) &&
		     ([id$resp_h, id$resp_p] !in allow_services_to || 
		      [id$orig_h, id$resp_p] !in allow_services_from) )
			{
			if ( inbound &&
			     service in terminate_successful_inbound_service )
				TerminateConnection::terminate_connection(c);

			++c$hot;
			if ( service in flag_successful_service )
				append_addl(c, flag_successful_service[service]);
			else
				append_addl(c, flag_successful_inbound_service[service]);
			}
		}

	else if ( state == APPL_ESTABLISHED ||
		  ((state == CONN_FINISHED || state == CONN_TIMEOUT ||
		    state == CONN_REUSED) &&
		   service != telnet && c$orig$size > 0 && c$resp$size > 0) )
		{
		# Connection established and has a non-trivial size.
		local orig = c$id$orig_h;
		local resp = c$id$resp_h;

		if ( [resp, service] in allow_services_to ||
		     [orig, service] in allow_services_from ||
		     [orig, resp, service] in allow_service_pairs ||
		     [orig, resp] in allow_pairs )
			return F;

		do_hot_check(c, resp, hot_srcs);
		do_hot_check(c, resp, hot_dsts);
		}

	return c$hot != 0;
	}


# Detection of a particular SSHv1 overflow attack.
global ssh_notice_hosts: table[addr] of count &default = 0;

const ssh_overflow_sizes = { 102575, 235695, } &redef;

event connection_finished(c: connection)
	{
	if ( c$id$resp_p == ssh && c$orig$size in ssh_overflow_sizes )
		{
		if ( ++ssh_notice_hosts[c$id$orig_h] == 1 )
			{
			NOTICE([$note=SSH_Overflow,
				$conn=c,
				$msg=fmt("%d -> %d bytes",
					c$orig$size, c$resp$size)]);
			}
		}
	}
