Created
June 2, 2016 15:05
-
-
Save JonZeolla/31c3d2977bbf18c97a675454aabca853 to your computer and use it in GitHub Desktop.
ALPHA: scan.bro with destination sampling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
##! TCP Scan detection. | |
# ..Authors: Sheharbano Khattak | |
# Seth Hall | |
# All the authors of the old scan.bro | |
# Modified by Jon Zeolla to add destination sampling, with | |
# https://www.bro.org/sphinx/_downloads/detect-sqli.bro as a template | |
# Foundation provided by http://mailman.icsi.berkeley.edu/pipermail/bro/2014-May/006996.html | |
@load base/frameworks/notice | |
@load base/frameworks/sumstats | |
@load base/utils/time | |
module Scan; | |
export { | |
redef enum Notice::Type += { | |
## Address scans detect that a host appears to be scanning some | |
## number of destinations on a single port. This notice is | |
## generated when more than :bro:id:`Scan::addr_scan_threshold` | |
## unique hosts are seen over the previous | |
## :bro:id:`Scan::addr_scan_interval` time range. | |
Address_Scan, | |
## Port scans detect that an attacking host appears to be | |
## scanning a single victim host on several ports. This notice | |
## is generated when an attacking host attempts to connect to | |
## :bro:id:`Scan::port_scan_threshold` | |
## unique ports on a single host over the previous | |
## :bro:id:`Scan::port_scan_interval` time range. | |
Port_Scan, | |
}; | |
## Failed connection attempts are tracked over this time interval for | |
## the address scan detection. A higher interval will detect slower | |
## scanners, but may also yield more false positives. | |
const addr_scan_interval = 5min &redef; | |
## Failed connection attempts are tracked over this time interval for | |
## the port scan detection. A higher interval will detect slower | |
## scanners, but may also yield more false positives. | |
const port_scan_interval = 5min &redef; | |
## The threshold of the unique number of hosts a scanning host has to | |
## have failed connections with on a single port. | |
const addr_scan_threshold = 25.0 &redef; | |
## The threshold of the number of unique ports a scanning host has to | |
## have failed connections with on a single victim host. | |
const port_scan_threshold = 15.0 &redef; | |
## Collecting samples will add extra data to notice emails | |
## by collecting some sample address scan destinations. Disable | |
## sample collection by setting this value to 0. | |
const collect_addr_scan_samples = 8 &redef; | |
global Scan::addr_scan_policy: hook(scanner: addr, victim: addr, scanned_port: port); | |
global Scan::port_scan_policy: hook(scanner: addr, victim: addr, scanned_port: port); | |
} | |
event bro_init() &priority=5 | |
{ | |
local r1: SumStats::Reducer = [$stream="scan.addr.fail", $apply=set(SumStats::UNIQUE, SumStats::SAMPLE), $unique_max=double_to_count(addr_scan_threshold+2), $num_samples=collect_addr_scan_samples]; | |
SumStats::create([$name="addr-scan", | |
$epoch=addr_scan_interval, | |
$reducers=set(r1), | |
$threshold_val(key: SumStats::Key, result: SumStats::Result) = | |
{ | |
return result["scan.addr.fail"]$unique+0.0; | |
}, | |
#$threshold_func=check_addr_scan_threshold, | |
$threshold=addr_scan_threshold, | |
$threshold_crossed(key: SumStats::Key, result: SumStats::Result) = | |
{ | |
local r = result["scan.addr.fail"]; | |
local side = Site::is_local_addr(key$host) ? "local" : "remote"; | |
local dur = duration_to_mins_secs(r$end-r$begin); | |
local message=fmt("%s scanned at least %d unique hosts on port %s in %s", key$host, r$unique, key$str, dur); | |
NOTICE([$note=Address_Scan, | |
$src=key$host, | |
$p=to_port(key$str), | |
$sub=side, | |
$msg=message, | |
$email_body_sections=vector(format_addr_scan_samples(r$samples)), | |
$identifier=cat(key$host)]); | |
}]); | |
# Note: port scans are tracked similar to: table[src_ip, dst_ip] of set(port); | |
local r2: SumStats::Reducer = [$stream="scan.port.fail", $apply=set(SumStats::UNIQUE), $unique_max=double_to_count(port_scan_threshold+2)]; | |
SumStats::create([$name="port-scan", | |
$epoch=port_scan_interval, | |
$reducers=set(r2), | |
$threshold_val(key: SumStats::Key, result: SumStats::Result) = | |
{ | |
return result["scan.port.fail"]$unique+0.0; | |
}, | |
$threshold=port_scan_threshold, | |
$threshold_crossed(key: SumStats::Key, result: SumStats::Result) = | |
{ | |
local r = result["scan.port.fail"]; | |
local side = Site::is_local_addr(key$host) ? "local" : "remote"; | |
local dur = duration_to_mins_secs(r$end-r$begin); | |
local message = fmt("%s scanned at least %d unique ports of host %s in %s", key$host, r$unique, key$str, dur); | |
NOTICE([$note=Port_Scan, | |
$src=key$host, | |
$dst=to_addr(key$str), | |
$sub=side, | |
$msg=message, | |
$identifier=cat(key$host)]); | |
}]); | |
} | |
function format_addr_scan_samples(samples: vector of SumStats::Observation): string | |
{ | |
local ret = "Address scan destination samples\n---------------------"; | |
for ( i in samples ) | |
ret += "\n" + samples[i]$str; | |
return ret; | |
} | |
function add_sumstats(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; | |
} | |
if ( hook Scan::addr_scan_policy(scanner, victim, scanned_port) ) | |
SumStats::observe("scan.addr.fail", [$host=scanner, $str=cat(scanned_port)], [$str=cat(victim)]); | |
if ( hook Scan::port_scan_policy(scanner, victim, scanned_port) ) | |
SumStats::observe("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; | |
} | |
event connection_attempt(c: connection) | |
{ | |
local is_reverse_scan = F; | |
if ( "H" in c$history ) | |
is_reverse_scan = T; | |
add_sumstats(c$id, is_reverse_scan); | |
} | |
event connection_rejected(c: connection) | |
{ | |
local is_reverse_scan = F; | |
if ( "s" in c$history ) | |
is_reverse_scan = T; | |
add_sumstats(c$id, is_reverse_scan); | |
} | |
event connection_reset(c: connection) | |
{ | |
if ( is_failed_conn(c) ) | |
add_sumstats(c$id, F); | |
else if ( is_reverse_failed_conn(c) ) | |
add_sumstats(c$id, T); | |
} | |
event connection_pending(c: connection) | |
{ | |
if ( is_failed_conn(c) ) | |
add_sumstats(c$id, F); | |
else if ( is_reverse_failed_conn(c) ) | |
add_sumstats(c$id, T); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment