mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
236 lines
8.1 KiB
Text
236 lines
8.1 KiB
Text
##! 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
|
||
# }
|