zeek/scripts/policy/misc/scan.bro

236 lines
8.1 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##! Scan detection
##!
##! ..Authors: Sheharbano Khattak
##! Seth Hall
##! All the authors of the old scan.bro
@load base/frameworks/notice
@load base/frameworks/metrics
module Scan;
export {
redef enum Notice::Type += {
## Address scans detect that a host appears to be scanning
## some number of other hosts on a single port.
Address_Scan,
## Port scans detect that a host appears to be scanning a
## single other host on numerous ports.
Port_Scan,
};
## Interval at which to watch for an address scan detection threshold to be crossed.
const addr_scan_interval = 5min &redef;
## Interval at which to watch for a port scan detection threshold to be crossed.
const port_scan_interval = 5min &redef;
## The threshold of a unique number of hosts a scanning host has to have failed
## connections with on a single port.
const addr_scan_threshold = 25 &redef;
## The threshold of a number of unique ports a scanning host has to have failed
## connections with on a single victim host.
const port_scan_threshold = 15 &redef;
## Custom threholds based on service for address scan. This is primarily
## useful for setting reduced thresholds for specific ports.
const addr_scan_custom_thresholds: table[port] of count &redef;
}
function check_addr_scan_threshold(index: Metrics::Index, val: Metrics::ResultVal): bool
{
# We don't need to do this if no custom thresholds are defined.
if ( |addr_scan_custom_thresholds| == 0 )
return F;
local service = to_port(index$str);
return ( service in addr_scan_custom_thresholds &&
val$sum > addr_scan_custom_thresholds[service] );
}
function addr_scan_threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal)
{
local side = Site::is_local_addr(index$host) ? "local" : "remote";
local message=fmt("%s scanned %d unique hosts on port %s", index$host, val$unique, index$str);
NOTICE([$note=Address_Scan,
$src=index$host,
$p=to_port(index$str),
$sub=side,
$msg=message,
$identifier=cat(index)]);
}
function port_scan_threshold_crossed(index: Metrics::Index, val: Metrics::ResultVal)
{
local side = Site::is_local_addr(index$host) ? "local" : "remote";
local message = fmt("%s scanned %d unique ports of host %s", index$host, val$unique, index$str);
NOTICE([$note=Port_Scan,
$src=index$host,
$dst=to_addr(index$str),
$sub=side,
$msg=message,
$identifier=cat(index)]);
}
event bro_init() &priority=5
{
# note=> Addr scan: table [src_ip, port] of set(dst);
# Add filters to the metrics so that the metrics framework knows how to
# determine when it looks like an actual attack and how to respond when
# thresholds are crossed.
Metrics::add_filter("scan.addr.fail", [$log=F,
$every=addr_scan_interval,
$measure=set(Metrics::UNIQUE),
$threshold_func=check_addr_scan_threshold,
$threshold=addr_scan_threshold,
$threshold_crossed=addr_scan_threshold_crossed]);
# note=> Port Sweep: table[src_ip, dst_ip] of set(port);
# Add filters to the metrics so that the metrics framework knows how to
# determine when it looks like an actual attack and how to respond when
# thresholds are crossed.
Metrics::add_filter("scan.port.fail", [$log=F,
$every=port_scan_interval,
$measure=set(Metrics::UNIQUE),
$threshold=port_scan_threshold,
$threshold_crossed=port_scan_threshold_crossed]);
}
function add_metrics(id: conn_id, reverse: bool)
{
local scanner = id$orig_h;
local victim = id$resp_h;
local scanned_port = id$resp_p;
if ( reverse )
{
scanner = id$resp_h;
victim = id$orig_h;
scanned_port = id$orig_p;
}
# Defaults to be implemented with a hook...
#local transport_layer_proto = get_port_transport_proto(service);
#if ( suppress_UDP_scan_checks && (transport_layer_proto == udp) )
# return F;
#else if ( suppress_TCP_scan_checks && (transport_layer_proto == tcp) )
# return F;
#else if ( suppress_ICMP_scan_checks && (transport_layer_proto == icmp) )
# return F;
# TODO: all of this whitelist/blacklist will be done
# through the upcoming hook mechanism
# Blacklisting/whitelisting services
#if ( |analyze_services| > 0 )
# {
# if ( service !in analyze_services )
# return F;
# }
#else if ( service in skip_services )
# return F;
#
## Blacklisting/whitelisting subnets
#if ( |analyze_subnets| > 0 && host !in analyze_subnets )
# return F;
# Probably do a hook point here?
Metrics::add_data("scan.addr.fail", [$host=scanner, $str=cat(scanned_port)], [$str=cat(victim)]);
# Probably do a hook point here?
Metrics::add_data("scan.port.fail", [$host=scanner, $str=cat(victim)], [$str=cat(scanned_port)]);
}
function is_failed_conn(c: connection): bool
{
# Sr || ( (hR || ShR) && (data not sent in any direction) )
if ( (c$orig$state == TCP_SYN_SENT && c$resp$state == TCP_RESET) ||
(((c$orig$state == TCP_RESET && c$resp$state == TCP_SYN_ACK_SENT) ||
(c$orig$state == TCP_RESET && c$resp$state == TCP_ESTABLISHED && "S" in c$history )
) && /[Dd]/ !in c$history )
)
return T;
return F;
}
function is_reverse_failed_conn(c: connection): bool
{
# reverse scan i.e. conn dest is the scanner
# sR || ( (Hr || sHr) && (data not sent in any direction) )
if ( (c$resp$state == TCP_SYN_SENT && c$orig$state == TCP_RESET) ||
(((c$resp$state == TCP_RESET && c$orig$state == TCP_SYN_ACK_SENT) ||
(c$resp$state == TCP_RESET && c$orig$state == TCP_ESTABLISHED && "s" in c$history )
) && /[Dd]/ !in c$history )
)
return T;
return F;
}
## Generated for an unsuccessful connection attempt. This
## event is raised when an originator unsuccessfully attempted
## to establish a connection. “Unsuccessful” is defined as at least
## tcp_attempt_delay seconds having elapsed since the originator
## first sent a connection establishment packet to the destination
## without seeing a reply.
event connection_attempt(c: connection)
{
local is_reverse_scan = F;
if ( "H" in c$history )
is_reverse_scan = T;
add_metrics(c$id, is_reverse_scan);
}
## Generated for a rejected TCP connection. This event
## is raised when an originator attempted to setup a TCP
## connection but the responder replied with a RST packet
## denying it.
event connection_rejected(c: connection)
{
local is_reverse_scan = F;
if ( "s" in c$history )
is_reverse_scan = T;
add_metrics(c$id, is_reverse_scan);
}
## Generated when an endpoint aborted a TCP connection.
## The event is raised when one endpoint of an *established*
## TCP connection aborted by sending a RST packet.
event connection_reset(c: connection)
{
if ( is_failed_conn(c) )
add_metrics(c$id, F);
else if ( is_reverse_failed_conn(c) )
add_metrics(c$id, T);
}
## Generated for each still-open connection when Bro terminates.
event connection_pending(c: connection)
{
if ( is_failed_conn(c) )
add_metrics(c$id, F);
else if ( is_reverse_failed_conn(c) )
add_metrics(c$id, T);
}
## Generated when a SYN-ACK packet is seen in response to a SYN
## packet during a TCP handshake. The final ACK of the handshake
## in response to SYN-ACK may or may not occur later, one way to
## tell is to check the history field of connection to see if the
## originator sent an ACK, indicated by A in the history string.
#event connection_established(c: connection)
# {
# # Not useful for scan (too early)
# }
## Generated when one endpoint of a TCP connection attempted
## to gracefully close the connection, but the other endpoint
## is in the TCP_INACTIVE state. This can happen due to split
## routing, in which Bro only sees one side of a connection.
#event connection_half_finished(c: connection)
# {
# # Half connections never were "established", so do scan-checking here.
# # I am not taking *f cases of c$history into account. Ask Seth if I should
# }