├── COPYING ├── README.md ├── files ├── LBL-subnets.csv-LATEST_BRO ├── knockknock.exceptions └── scan-portexclude ├── scripts ├── __load__.zeek ├── avoid-scan-FP.zeek ├── blocked-nets.zeek ├── check-addressscan.zeek ├── check-backscatter.zeek ├── check-knock.zeek ├── check-knocksubnet.zeek ├── check-landmine.zeek ├── check-lowporttroll.zeek ├── check-port-knock.zeek ├── check-portscan.zeek ├── check-scan-impl.zeek ├── check-scan.zeek ├── conn-history.zeek ├── debug.zeek ├── expire-known-scanners.zeek ├── host-profiling.zeek ├── hotsubnets.zeek ├── identify-web-spiders.zeek ├── interest-conflict.zeek ├── interests.zeek ├── netcontrol-scan-rules.zeek ├── old-scan.zeek ├── port-address-scan.zeek ├── port-flux-density.zeek ├── port-scan.zeek ├── scan-base.zeek ├── scan-blocked-hotsubnets.zeek ├── scan-config.zeek ├── scan-inputs.zeek ├── scan-spikes.zeek ├── site-subnets.zeek ├── skip-services.zeek ├── ss.zeek ├── stats.zeek ├── trw-impl.zeek └── trw.zeek └── zkg.meta /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 1995-2020, The Regents of the University of California 2 | through the Lawrence Berkeley National Laboratory. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | (1) Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | (2) Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | (3) Neither the name of the University of California, Lawrence Berkeley 15 | National Laboratory, U.S. Dept. of Energy, nor the names of 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | 31 | Note that some files in the distribution may carry their own copyright 32 | notices. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scan-NG 2 | 3 | Now works with zeek-3.x.x 4 | 5 | to load in your local.zeek (or site.zeek) policy 6 | 7 | @load scan-NG/scripts 8 | 9 | To tune scan-detection you can adjust/define variables in: scan-config.zeek 10 | 11 | -------------------------------------------------------------------------------- /files/LBL-subnets.csv-LATEST_BRO: -------------------------------------------------------------------------------- 1 | #fields Network Gateway Enclaves Use 2 | 128.3.0.1/24 128.3.0.1 LBL Zone 1 3 | -------------------------------------------------------------------------------- /files/knockknock.exceptions: -------------------------------------------------------------------------------- 1 | #fields exclude_ip exclude_port t comment 2 | 131.243.1.1 0 tcp blah 3 | 131.243.2.5 0 tcp blah2 4 | -------------------------------------------------------------------------------- /files/scan-portexclude: -------------------------------------------------------------------------------- 1 | #fields skip_port t comment 2 | 1094 tcp # xrootd 3 | 1095 tcp # xrootd 4 | -------------------------------------------------------------------------------- /scripts/__load__.zeek: -------------------------------------------------------------------------------- 1 | #v3 2 | #redef exit_only_after_terminate=T ; 3 | 4 | redef global_hash_seed = "bloomhash"; 5 | redef Log::print_to_log = Log::REDIRECT_STDOUT; 6 | 7 | @load policy/frameworks/notice/actions/drop 8 | 9 | @load ./debug 10 | @load ./site-subnets 11 | @load ./host-profiling 12 | @load ./port-flux-density 13 | 14 | @load ./stats 15 | 16 | @load ./scan-base 17 | @load ./expire-known-scanners.zeek 18 | @load ./ss.zeek 19 | @load ./conn-history 20 | 21 | @load ./scan-inputs 22 | @load ./skip-services 23 | @load ./blocked-nets.zeek 24 | @load ./hotsubnets.zeek 25 | @load ./scan-spikes 26 | 27 | @load ./identify-web-spiders 28 | 29 | @load ./check-knock 30 | @load ./check-knocksubnet.zeek 31 | @load ./check-backscatter 32 | #@load ./port-address-scan.zeek 33 | @load ./check-addressscan 34 | @load ./check-lowporttroll 35 | @load ./check-portscan 36 | #@load ./port-scan 37 | @load ./trw 38 | @load ./check-landmine 39 | 40 | @load ./check-scan-impl 41 | @load ./check-scan 42 | @load ./scan-config 43 | @load ./interests 44 | @load ./interest-conflict.zeek 45 | 46 | 47 | #@load ./lbl.scan-policy.zeek 48 | #@load ./lbl.scan-policy-drop.zeek 49 | 50 | #@load ./netcontrol-scan-rules 51 | #@load ./check-port-knock 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /scripts/avoid-scan-FP.zeek: -------------------------------------------------------------------------------- 1 | module History; 2 | 3 | export { 4 | 5 | global institutional_servers: set[addr] = { 6 | 7 | }; 8 | } 9 | 10 | event connection_established (c: connection) 11 | { 12 | 13 | local orig = c$id$orig_h ; 14 | local resp = c$id$resp_h ; 15 | 16 | if (resp !in Site::local_nets) 17 | return ; 18 | 19 | if (resp in institutional_servers) 20 | add_to_bloom(orig); 21 | } 22 | -------------------------------------------------------------------------------- /scripts/blocked-nets.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | #redef exit_only_after_terminate = T ; 4 | 5 | export { 6 | redef enum Notice::Type += { 7 | BlocknetsIP, 8 | BlocknetsFileReadFail, 9 | }; 10 | 11 | # /YURTT/feeds/BRO-feeds/WIRED.blocknet 12 | # header info 13 | #fields NETWORK BLOCK_FLAVOR 14 | #2.187.44.0/24 blocknet 15 | #2.187.45.0/24 blocknet 16 | 17 | type blocknet_Idx: record { 18 | NETWORK: subnet; 19 | }; 20 | 21 | type blocknet_Val: record { 22 | NETWORK: subnet; 23 | BLOCK_FLAVOR: string &optional; 24 | }; 25 | 26 | global blocked_nets: table[subnet] of blocknet_Val = table() &redef; 27 | global blocknet_feed = "/YURT/feeds/BRO-feeds/WIRED.blocknet" &redef; 28 | } 29 | 30 | # FAILURE-CHECK 31 | # we catch the error in subnet feed is empty and populate blocked_nets with local_nets 32 | # so that LandMine detection doesn't block accidently 33 | 34 | hook Notice::policy(n: Notice::Info) 35 | { 36 | if ( n$note == Scan::BlocknetsFileReadFail ) { 37 | add n$actions[Notice::ACTION_EMAIL]; 38 | } 39 | } 40 | 41 | # handle this failure 42 | # Reporter::WARNING /YURT/feeds/BRO-feeds/WIRED.blocknet.2/Input::READER_ASCII: 43 | # Init: cannot open /YURT/feeds/BRO-feeds/WIRED.blocknet.2 (empty) 44 | 45 | event reporter_warning(t: time, msg: string, location: string) 46 | { 47 | if ( /WIRED.blocknet.*\/Input::READER_ASCII: Init: cannot open/ in msg ) { 48 | NOTICE([$note=BlocknetsFileReadFail, $msg=fmt("%s", msg)]); 49 | } 50 | } 51 | 52 | event Input::end_of_data(name: string, source: string) 53 | { 54 | if ( /WIRED.blocknet/ in name ) { 55 | print fmt("name=%s, source=%s, records=%s", name, source, |source|); 56 | } 57 | # since subnet table is zero size 58 | # we poulate with local_nets 59 | 60 | # FIXME 61 | #if (|Scan::blocked_nets| == 0) 62 | #{ 63 | # for (nets in Site::local_nets) 64 | # { local sv: blocknet_Val ; 65 | # blocked_nets[nets] = sv ; 66 | # } 67 | #} 68 | } 69 | 70 | event line(description: Input::TableDescription, tpe: Input::Event, 71 | left: blocknet_Idx, right: blocknet_Val) 72 | { 73 | local msg: string; 74 | 75 | if ( tpe == Input::EVENT_NEW ) { #print fmt ("NEW"); 76 | } 77 | 78 | if ( tpe == Input::EVENT_CHANGED ) { #print fmt ("CHANGED"); 79 | } 80 | 81 | if ( tpe == Input::EVENT_REMOVED ) { #print fmt ("REMOVED"); 82 | } 83 | } 84 | 85 | event zeek_init() &priority=10 86 | { 87 | Input::add_table([ 88 | $source=blocknet_feed, 89 | $name="blocked_nets", 90 | $idx=blocknet_Idx, 91 | $val=blocknet_Val, 92 | $destination=blocked_nets, 93 | $mode=Input::REREAD, 94 | $ev=line]); 95 | 96 | # Input::add_table([$source=blocknet_feed, $name="blocked_nets", 97 | # $idx=blocknet_Idx, $val=blocknet_Val, $destination=blocked_nets, $mode=Input::REREAD]); 98 | # $pred(typ: Input::Event, left: blocknet_Idx, right: blocknet_Val = 99 | # { left$epo = to_lower(left$epo); return T;) }]); 100 | } 101 | 102 | event zeek_done() 103 | { 104 | #print fmt("bro-done"); 105 | #print fmt("digested %s records in blocked_nets", |Scan::blocked_nets|); 106 | #print fmt("blocked_nets %s", Scan::blocked_nets); 107 | } 108 | 109 | -------------------------------------------------------------------------------- /scripts/check-addressscan.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | global activate_AddressScan = T &redef; 5 | 6 | redef enum Notice::Type += { 7 | AddressScan, # the source has scanned a number of addrs 8 | ScanSummary, # summary of scanning activity 9 | ShutdownThresh, # source reached shut_down_thresh 10 | }; 11 | 12 | # Which services should be analyzed when detecting scanning 13 | # (not consulted if analyze_all_services is set). 14 | const analyze_services: set[port] &redef; 15 | const analyze_all_services = T &redef; 16 | 17 | # Report a scan of peers at each of these points. 18 | const report_peer_scan: vector of count = { 19 | 20, 20 | 30, 21 | 50, 22 | 100, 23 | 1000, 24 | 10000, 25 | 50000, 26 | 100000, 27 | 250000, 28 | 500000, 29 | 1000000, 30 | } &redef; 31 | 32 | const report_outbound_peer_scan: vector of count = { 33 | 20, 34 | 30, 35 | 50, 36 | 100, 37 | 1000, 38 | 10000, 39 | } &redef; 40 | 41 | global thresh_check: function(v: vector of count, idx: table[addr] of count, 42 | orig: addr, n: count): bool; 43 | 44 | global rps_idx: table[addr] of count &default=1 &read_expire=1 days &redef; 45 | global rops_idx: table[addr] of count &default=1 &read_expire=1 days &redef; 46 | 47 | # Expire functions that trigger summaries. 48 | global scan_sum: function(t: table[addr] of set[addr], orig: addr): interval; 49 | 50 | # scan storage containers 51 | global distinct_peers: table[addr] of set[addr] &read_expire=1 days 52 | &expire_func=scan_sum &redef; 53 | 54 | global c_distinct_peers: table[addr] of opaque of cardinality 55 | &default=function(n: any): opaque of cardinality { 56 | return hll_cardinality_init(0.1, 0.99); 57 | } &read_expire=1 day; # &expire_func=scan_sum &redef; 58 | 59 | const scan_summary_trigger = 25 &redef; 60 | 61 | global shut_down_thresh_reached: table[addr] of bool &default=F; 62 | 63 | # Raise ShutdownThresh after this many failed attempts 64 | const shut_down_thresh = 100 &redef; 65 | 66 | # addressscan 67 | # Ignore address scanners for further scan detection after 68 | # scanning this many hosts. 69 | # 0 disables. 70 | const ignore_scanners_threshold = 0 &redef; 71 | 72 | # changed this to use hyperloglog functions 73 | # global distinct_peers: table[addr] of set[addr] 74 | # &read_expire = 1 day &expire_func=scan_sum &redef; 75 | 76 | # global distinct_peers: table[addr] of opaque of cardinality 77 | # &default = function(n: any): opaque of cardinality { return hll_cardinality_init(0.1, 0.99); } 78 | # &read_expire = 1 day &expire_func=scan_sum &redef; 79 | } 80 | 81 | # To recognize whether a certain threshhold vector (e.g. report_peer_scans) 82 | # has been transgressed, a global variable containing the next vector index 83 | # (idx) must be incremented. This cumbersome mechanism is necessary because 84 | # values naturally don't increment by one (e.g. replayed table merges). 85 | function thresh_check(v: vector of count, idx: table[addr] of count, orig: addr, 86 | n: count): bool 87 | { 88 | if ( ignore_scanners_threshold > 0 && n > ignore_scanners_threshold ) { 89 | #ignore_addr(orig); 90 | return F; 91 | } 92 | 93 | if ( idx[orig] <= |v| && n >= v[idx[orig]] ) { 94 | ++idx[orig]; 95 | return T; 96 | } else 97 | return F; 98 | } 99 | 100 | function scan_sum(t: table[addr] of set[addr], orig: addr): interval 101 | { 102 | # log_reporter(fmt("scan_sum invoked for %s", t[orig]),0); 103 | 104 | local num_distinct_peers = orig in t ? |t[orig]| : 0; 105 | 106 | if ( num_distinct_peers >= scan_summary_trigger ) { 107 | NOTICE([ 108 | $note=ScanSummary, 109 | $src=orig, 110 | $n=num_distinct_peers, 111 | $msg=fmt("%s scanned a total of %d hosts", orig, num_distinct_peers)]); 112 | } 113 | return 0 secs; 114 | } 115 | 116 | function check_address_scan_thresholds(orig: addr, resp: addr, outbound: bool, 117 | n: count): bool 118 | { 119 | #A if (gather_statistics) 120 | # s_counters$c_addressscan_core+= 1 ; 121 | 122 | local address_scan = F; 123 | 124 | if ( outbound && # inside host scanning out? 125 | thresh_check(report_outbound_peer_scan, rops_idx, orig, n) ) 126 | address_scan = T; 127 | 128 | if ( ! outbound && thresh_check(report_peer_scan, rps_idx, orig, n) ) 129 | address_scan = T; 130 | 131 | return address_scan; 132 | } 133 | 134 | # filterate_AddresssScan runs on workers and does all the pre-filtering for 135 | # potential scan candidates 136 | 137 | function filterate_AddressScan(c: connection, established: bool, reverse: bool): string 138 | { 139 | 140 | 141 | # lets handle established connections differently for now 142 | # since we have many corner caseses around it 143 | if ( established) 144 | return ""; 145 | 146 | #A if (gather_statistics) 147 | # s_counters$c_addressscan_filterate += 1 ; 148 | 149 | # only deal with tcp 150 | local trans = get_port_transport_proto(c$id$resp_p); 151 | 152 | if ( trans != tcp ) 153 | return ""; 154 | 155 | if (!( c$history == "S" || c$history == "SW" || 156 | c$history == "Sr" || c$history == "SWr")) 157 | return "" ; 158 | 159 | local id = c$id; 160 | 161 | local service = "ftp-data" in c$service ? 20/tcp : ( reverse ? id$orig_p : 162 | id$resp_p ); 163 | local rev_service = reverse ? id$resp_p : id$orig_p; 164 | local orig = reverse ? id$resp_h : id$orig_h; 165 | local resp = reverse ? id$orig_h : id$resp_h; 166 | local outbound = Site::is_local_addr(orig); 167 | local orig_p = c$id$orig_p; 168 | 169 | # unless a known_scanner consider everything a scanner 170 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) 171 | return ""; 172 | 173 | # we do not watch for local address 174 | # we will do internal scan-detection seperately 175 | # using all-check 176 | 177 | if ( Site::is_local_addr(orig) ) 178 | return ""; 179 | 180 | # optional filters to reduce load on manager 181 | # we ignore all darknet connections since LandMine will take care of it 182 | # ( check if Scan::activate_LandMine = T ; ) 183 | 184 | # TODO 185 | #if (Scan::activate_LandMine && is_darknet(resp)) 186 | if ( is_darknet(resp) ) 187 | return ""; 188 | 189 | # we can ignore all the non-existing services since knockknock can take 190 | # care of it 191 | 192 | #if (Scan::activate_KnockKnock && resp in Site::host_profiles) 193 | # return "" ; 194 | 195 | # issue is how to identify legit connections and not send those 196 | if ( established ) 197 | return ""; 198 | 199 | # The following works better than using get_conn_transport_proto() 200 | # because c might not correspond to an active connection (which 201 | # causes the function to fail). 202 | # aashish UDP 203 | if ( suppress_UDP_scan_checks && service >= 0/udp && service <= 65535/udp ) 204 | return ""; 205 | 206 | if ( service in skip_services && ! outbound ) 207 | return ""; 208 | 209 | if ( outbound && service in skip_outbound_services ) 210 | return ""; 211 | 212 | if ( orig in skip_scan_sources ) 213 | return ""; 214 | 215 | if ( orig in skip_scan_nets ) 216 | return ""; 217 | 218 | # Don't include well known server/ports for scanning purposes. 219 | if ( ! outbound && [resp, service] in skip_dest_server_ports ) 220 | return ""; 221 | 222 | if ( orig in ignored_scanners ) 223 | return ""; 224 | 225 | return "A"; 226 | } 227 | 228 | # runs on manager to consolidate all connections which workers see 229 | function check_AddressScan(cid: conn_id, established: bool, reverse: bool): bool 230 | { 231 | #A if (gather_statistics) 232 | # s_counters$c_addressscan_checkscan += 1 ; 233 | 234 | local trans = get_port_transport_proto(cid$orig_p); 235 | 236 | if ( trans != tcp ) 237 | return F; 238 | 239 | local id = cid; 240 | local result = F; 241 | 242 | local service = reverse ? id$orig_p : id$resp_p; 243 | local rev_service = reverse ? id$resp_p : id$orig_p; 244 | local orig = reverse ? id$resp_h : id$orig_h; 245 | local resp = reverse ? id$orig_h : id$resp_h; 246 | local outbound = Site::is_local_addr(orig); 247 | local orig_p = cid$orig_p; 248 | 249 | # TODO 250 | # if (orig in c_distinct_backscatter_peers) 251 | # if (orig_p in c_distinct_backscatter_peers[orig]) 252 | # { 253 | #if (|distinct_backscatter_peers[orig][orig_p]| < 2) 254 | # local bsc = double_to_count(hll_cardinality_estimate(c_distinct_backscatter_peers[orig])) ; 255 | # if ( bsc < 2) 256 | # result = F ; 257 | # } 258 | # 259 | # log_reporter(fmt("add_to_addressscan_cache: check_AddressScan: %s", c$id),0); 260 | 261 | local resp_count = double_to_count(hll_cardinality_estimate( 262 | c_distinct_peers[orig])); 263 | 264 | local n = 0; 265 | 266 | if ( ( ! established ) && # not established, service not expressly allowed 267 | # not known peer set 268 | ( orig !in distinct_peers || resp !in distinct_peers[orig] ) 269 | || ( orig !in c_distinct_peers || resp_count != 0.0 ) 270 | && # want to consider service for scan detection 271 | ( analyze_all_services || service in analyze_services ) ) { 272 | if ( enable_big_tables ) { 273 | if ( orig !in distinct_peers ) 274 | distinct_peers[orig] = set(); 275 | 276 | if ( resp !in distinct_peers[orig] ) 277 | add distinct_peers[orig][resp]; 278 | 279 | n = |distinct_peers[orig]|; 280 | } else { 281 | if ( orig !in c_distinct_peers ) { 282 | local cp: opaque of cardinality = hll_cardinality_init(0.1, 0.99); 283 | c_distinct_peers[orig] = cp; 284 | } 285 | 286 | hll_cardinality_add(c_distinct_peers[orig], resp); 287 | 288 | n = double_to_count(hll_cardinality_estimate(c_distinct_peers[orig])); 289 | } 290 | 291 | local address_scan_result = check_address_scan_thresholds(orig, resp, 292 | outbound, n); 293 | 294 | if ( address_scan_result ) { 295 | # we block likely_server_port scanners at higher threshold than other ports) 296 | if ( ( service in likely_server_ports && n > 99 ) 297 | || ( service !in likely_server_ports ) ) { 298 | NOTICE([ 299 | $note=AddressScan, 300 | $src=orig, 301 | $id=cid, 302 | $p=service, 303 | $n=n, 304 | $msg=fmt("%s has scanned %d hosts (%s)", orig, n, service)]); 305 | 306 | #log_reporter (fmt ("NOTICE: FOUND AddressScan: %s", orig),0); 307 | 308 | result = T; 309 | } 310 | } 311 | } 312 | 313 | # Check for threshold if not outbound. 314 | if ( ! shut_down_thresh_reached[orig] 315 | && n >= shut_down_thresh 316 | && ! outbound 317 | && orig !in Site::neighbor_nets ) { 318 | shut_down_thresh_reached[orig] = T; 319 | local msg = fmt("shutdown threshold reached for %s", orig); 320 | NOTICE([$note=ShutdownThresh, $src=orig, $p=service, $msg=msg]); 321 | 322 | result = T; 323 | 324 | Scan::add_to_known_scanners(orig, "ShutdownThresh"); 325 | Scan::known_scanners[orig]$detect_ts = network_time(); 326 | } 327 | 328 | # backscater check - we don't want to send events to manager for addressscan if 329 | # this is a backscatter traffic 330 | # which is generally characterized by |s_port| == 1 331 | 332 | return result; 333 | } 334 | # 335 | # if ( orig in Scan::known_scanners) 336 | # if (Scan::known_scanners[orig]$status) 337 | # { return; } 338 | # 339 | # if ([orig] !in distinct_peers) 340 | # distinct_peers[orig]=set() ; 341 | # 342 | # add distinct_peers[orig][resp]; 343 | # 344 | # local n = |distinct_peers[orig]|; 345 | # 346 | # local result = check_address_scan_thresholds(orig, resp, outbound, n) ; 347 | # 348 | # if (result) 349 | # { 350 | # NOTICE([$note=AddressScan, 351 | # $src=orig, $p=service, $n=n, 352 | # $msg=fmt("%s has scanned %d hosts (%s)", orig, n, service)]); 353 | # } 354 | 355 | # events for scan detections 356 | 357 | # event connection_established(c: connection) 358 | # { 359 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 360 | # Scan::check_AddressScan(c$id, T, is_reverse_scan); 361 | # 362 | # local trans = get_port_transport_proto(c$id$orig_p); 363 | # #if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 364 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 365 | # } 366 | # 367 | #event partial_connection(c: connection) 368 | # { 369 | # Scan::check_AddressScan(c$id, T, F); 370 | # } 371 | # 372 | #event connection_attempt(c: connection) 373 | # { 374 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 375 | # Scan::check_AddressScan(c$id, F, is_reverse_scan); 376 | # 377 | # local trans = get_port_transport_proto(c$id$orig_p); 378 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 379 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 380 | # } 381 | # 382 | #event connection_half_finished(c: connection) 383 | # { 384 | # # Half connections never were "established", so do scan-checking here. 385 | # Scan::check_AddressScan(c$id, F, F); 386 | # } 387 | # 388 | #event connection_rejected(c: connection) 389 | # { 390 | # local is_reverse_scan = (c$orig$state == TCP_RESET && c$id$resp_p !in likely_server_ports); 391 | # 392 | # Scan::check_AddressScan(c$id, F, is_reverse_scan); 393 | # 394 | # #local trans = get_port_transport_proto(c$id$orig_p); 395 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 396 | # # TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 397 | # } 398 | # 399 | #event connection_reset(c: connection) 400 | # { 401 | # if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE ) 402 | # { 403 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 404 | # # We never heard from one side - that looks like a scan. 405 | # Scan::check_AddressScan(c$id, c$orig$size + c$resp$size > 0, is_reverse_scan); 406 | # } 407 | # } 408 | # 409 | #event connection_pending(c: connection) 410 | # { 411 | # if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE ) 412 | # Scan::check_AddressScan(c$id, F, F); 413 | # } 414 | # 415 | # 416 | -------------------------------------------------------------------------------- /scripts/check-backscatter.zeek: -------------------------------------------------------------------------------- 1 | # policy to determine backscatter traffic which is mainly charaterized by: 2 | # No Syn seen from Originator but only syn-ack, or Fin seen 3 | # Backscatter is primarily result of someone spoofing LBL address space to participate 4 | # in a DoS. 5 | # use of this policy is so that we needlessly don't block a victim IP 6 | # intention of this policy is to mostly provide scan-detection an advice about backscatter status 7 | 8 | # heuristics also include observation that a backscatter which is result of DoS generally 9 | # is limited to only a few selected ports 10 | # so we count how many uniq src ports are seen. IF > BackscatterThreshold, we ignore the 11 | # remaining traffic 12 | 13 | module Scan; 14 | 15 | export { 16 | #const DEBUG = 1; 17 | global activate_Backscatter = T &redef; 18 | 19 | redef enum Notice::Type += { 20 | Backscatter, 21 | }; 22 | 23 | # Reverse (SYN-ack) scans seen from these ports are considered 24 | # to reflect possible SYN-flooding backscatter, and not true 25 | # (stealth) scans. 26 | const backscatter_ports: set[port] = { } &redef; 27 | const report_backscatter: vector of count = { } &redef; 28 | 29 | global backscatter: table[addr] of count &create_expire=1 day; 30 | global distinct_backscatter_peers: table[addr] of table[port] of set[addr] = table() 31 | &create_expire=1 day; 32 | 33 | type bs: table[port] of opaque of cardinality &default=function(p: port): opaque of 34 | cardinality { 35 | return hll_cardinality_init(0.1, 0.99); 36 | }; 37 | global c_distinct_backscatter_peers: table[addr] of bs &create_expire=1 day; 38 | 39 | # backscatter traffic doesn't generally have > 10 uniq src port 40 | # also to prevent the table from bloating up 41 | const BACKSCATTER_PORT_THRESH = 2; 42 | const BACKSCATTER_THRESH = 10; 43 | 44 | global check_Backscatter: function(cid: conn_id, established: bool, 45 | reverse: bool): bool; 46 | global filterate_Backscatter: function(c: connection, darknet: bool): string; 47 | } 48 | 49 | event zeek_done () 50 | { 51 | print fmt ("backscatter: %s", distinct_backscatter_peers); 52 | } 53 | 54 | function c_check_backscatter_thresholds(orig: addr, s_port: port, resp: addr): bool 55 | { 56 | if ( gather_statistics ) 57 | s_counters$c_backscat_core += 1; 58 | 59 | local result = F; 60 | 61 | if ( orig !in c_distinct_backscatter_peers ) 62 | c_distinct_backscatter_peers[orig] = table(); 63 | 64 | if ( |c_distinct_backscatter_peers[orig]| > BACKSCATTER_PORT_THRESH ) { 65 | return F; 66 | } 67 | 68 | # track upto 2 uniq src port 69 | if ( |c_distinct_backscatter_peers[orig]| <= BACKSCATTER_PORT_THRESH ) { 70 | if ( s_port !in c_distinct_backscatter_peers[orig] ) { 71 | local cp: opaque of cardinality = hll_cardinality_init(0.1, 0.99); 72 | c_distinct_backscatter_peers[orig][s_port] = cp; 73 | } 74 | 75 | hll_cardinality_add(c_distinct_backscatter_peers[orig][s_port], resp); 76 | 77 | local d_val = double_to_count(hll_cardinality_estimate( 78 | c_distinct_backscatter_peers[orig][s_port])); 79 | 80 | if ( d_val >= BACKSCATTER_THRESH ) { 81 | #print fmt("CARDINAL: backscatter seen from %s (%d port: %s)", orig, |distinct_backscatter_peers[orig]|, s_port) ; 82 | NOTICE([ 83 | $note=Backscatter, 84 | $src=orig, 85 | $p=s_port, 86 | $msg=fmt("backscatter seen from %s (%d port: %s)", orig, d_val, s_port)]); 87 | 88 | # is a scanner now 89 | result = T; 90 | } 91 | } 92 | 93 | return result; 94 | } 95 | 96 | function check_backscatter_thresholds(orig: addr, rev_svc: port, resp: addr): bool 97 | { 98 | if ( gather_statistics ) 99 | s_counters$c_backscat_core += 1; 100 | 101 | local result = F; 102 | 103 | if ( orig !in distinct_backscatter_peers ) 104 | distinct_backscatter_peers[orig] = table(); 105 | 106 | if ( |distinct_backscatter_peers[orig]| > BACKSCATTER_PORT_THRESH ) { 107 | return F; 108 | } 109 | 110 | # track upto 2 uniq src port 111 | if ( |distinct_backscatter_peers[orig]| <= BACKSCATTER_PORT_THRESH ) { 112 | if ( rev_svc !in distinct_backscatter_peers[orig] ) 113 | distinct_backscatter_peers[orig][rev_svc] = set(); 114 | 115 | if ( resp !in distinct_backscatter_peers[orig][rev_svc] ) { 116 | add distinct_backscatter_peers[orig][rev_svc][resp]; 117 | 118 | if ( |distinct_backscatter_peers[orig][rev_svc]| >= 119 | BACKSCATTER_THRESH ) { 120 | NOTICE([ 121 | $note=Backscatter, 122 | $src=orig, 123 | $p=rev_svc, 124 | $msg=fmt("backscatter seen from %s (%d port: %s)", orig, |distinct_backscatter_peers[orig]|, rev_svc)]); 125 | # is a scanner now 126 | result = T; 127 | log_reporter(fmt("NOTICE: FOUND Backscatter : %s, result : %s", orig, result), 0); 128 | } 129 | } 130 | } 131 | 132 | return result; 133 | } 134 | 135 | function check_Backscatter(cid: conn_id, established: bool, reverse: bool): bool 136 | { 137 | #log_reporter(fmt("in check_Backscatter: known_scanner: %s", cid),0); 138 | 139 | if ( gather_statistics ) 140 | s_counters$c_backscat_checkscan += 1; 141 | 142 | local orig = cid$orig_h; 143 | local resp = cid$resp_h; 144 | local d_port = cid$resp_p; 145 | local rev_svc = cid$orig_p; 146 | 147 | #already identified as scanner no need to proceed further 148 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) { 149 | return F; 150 | } 151 | 152 | if ( orig in backscatter ) 153 | return F; 154 | 155 | local result: bool; 156 | 157 | if ( enable_big_tables ) { 158 | result = check_backscatter_thresholds(orig, rev_svc, resp); 159 | } else { 160 | result = c_check_backscatter_thresholds(orig, rev_svc, resp); 161 | } 162 | 163 | #log_reporter(fmt("in check_Backscatter log result is : %s for %s", result, cid),0); 164 | 165 | if ( result ) { 166 | #log_reporter(fmt("in check_Backscatter log result II is : %s for %s", result, cid),0); 167 | #add_to_known_scanners(orig, "BackscatterScan"); 168 | backscatter[orig] = 1; 169 | } 170 | 171 | return result; 172 | } 173 | 174 | #[id=[orig_h=167.114.206.157, orig_p=9010/tcp, resp_h=128.3.64.182, resp_p=49003/tcp], 175 | #orig=[size=0, state=6, num_pkts=2, num_bytes_ip=80, flow_label=0], 176 | #resp=[size=0, state=0, num_pkts=0, num_bytes_ip=0, flow_label=0], 177 | #start_time=1456189676.188253, 178 | #duration=0.000003, 179 | #service={\x0a\x0a}, 180 | #history=R, 181 | #uid=CqjpWA43cXknfcEVIh, 182 | #conn=[ts=1456189676.188253, uid=CqjpWA43cXknfcEVIh, id=[orig_h=167.114.206.157, orig_p=9010/tcp, resp_h=128.3.64.182, resp_p=49003/tcp], proto=tcp, service=, duration=0.000003, orig_bytes=0, resp_bytes=0, conn_state=RSTOS0, local_orig=, local_resp=, missed_bytes=0, history=R, orig_pkts=2, orig_ip_bytes=80, resp_pkts=0, resp_ip_bytes=0, 183 | # 184 | 185 | function filterate_Backscatter(c: connection, darknet: bool): string 186 | { 187 | if ( ! activate_Backscatter ) 188 | return ""; 189 | 190 | if ( gather_statistics ) 191 | s_counters$c_backscat_filterate += 1; 192 | 193 | local orig = c$id$orig_h; 194 | local resp = c$id$resp_h; 195 | local d_port = c$id$resp_p; 196 | local s_port = c$id$orig_p; 197 | 198 | if ( get_port_transport_proto(c$id$resp_p) != tcp ) 199 | return ""; 200 | 201 | # internal host scanning handled seperately 202 | if ( Site::is_local_addr(c$id$orig_h) ) 203 | return ""; 204 | 205 | if ( ! darknet ) { 206 | # only worry about TCP connections 207 | # we deal with udp and icmp scanners differently 208 | 209 | # Blocked on border router - perma firewalled 210 | #if (d_port in ignore_already_blocked_ports) 211 | # return ""; 212 | 213 | # a) full established conns not interesting 214 | if ( c$resp$state == TCP_ESTABLISHED ) { 215 | return ""; 216 | } 217 | 218 | # b) full established conns not interesting 219 | # 2022-08-24 added S0 - rather flag as scanner 220 | # than backscatter 221 | if ( c?$conn && c$conn?$conn_state && /SF|S0/ in c$conn$conn_state ) { 222 | return ""; 223 | } 224 | 225 | local state = ""; 226 | if ( c?$conn && c$conn?$conn_state ) 227 | state = c$conn$conn_state; 228 | 229 | local resp_bytes = c$resp$size; 230 | 231 | # mid stream traffic - ignore 232 | if ( state == "OTH" && resp_bytes > 0 ) { 233 | return ""; 234 | } 235 | } 236 | 237 | #print fmt ("ORIG: %s", print_state(c$orig$state, c$conn$proto)); 238 | #print fmt ("RESP: %s", print_state(c$resp$state, c$conn$proto)); 239 | 240 | # tmp if (state == "REJ" && resp_bytes >0 ) 241 | # { print fmt ("REJ + Bytes: %s", c$id); } 242 | 243 | #if (state == "RSTR" && resp_bytes >0 ) 244 | #{ print fmt ("RSTR + Bytes: %s", c$id); } 245 | 246 | # 247 | #if (state == "OTH" && resp_bytes == 0 ) 248 | #{ print fmt ("OTH + 0 resp_bytes: %s", c$id); } 249 | 250 | if ( ( c$orig$state == TCP_SYN_ACK_SENT && c$resp$state == TCP_INACTIVE ) 251 | #|| ( c$orig$state == TCP_SYN_SENT && c$resp$state == TCP_INACTIVE ) 252 | || ( c$history == "F" || c$history == "R" ) 253 | || ( c$history == "H" && /s|a/ !in c$history ) ) { 254 | #if (check_Backscatter(c$id, F, F)) 255 | return "B"; 256 | } 257 | 258 | # 2016-05-23 we dont want to run backscatter on workers 259 | #else 260 | # { 261 | # check_Backscatter(c$id, F, F) ; 262 | # return "B" ; 263 | # } 264 | 265 | return ""; 266 | } 267 | 268 | # 2023-09-16 ash 269 | #@if ( ! Cluster::is_enabled() ) 270 | #event connection_state_remove(c: connection) 271 | #{ 272 | # check_Backscatter(c$id, F, F); 273 | #} 274 | #@endif 275 | 276 | 277 | #event connection_established(c: connection) 278 | # { 279 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 280 | # Scan::check_scan(c, T, is_reverse_scan); 281 | # 282 | # #local trans = get_port_transport_proto(c$id$orig_p); 283 | # #if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 284 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 285 | # } 286 | 287 | #event partial_connection(c: connection) 288 | # { 289 | # Scan::check_scan(c, T, F); 290 | # } 291 | # 292 | #event connection_attempt(c: connection) 293 | # { 294 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 295 | # Scan::check_scan(c, F, is_reverse_scan); 296 | # 297 | # #local trans = get_port_transport_proto(c$id$orig_p); 298 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 299 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 300 | # } 301 | # 302 | #event connection_half_finished(c: connection) 303 | # { 304 | # # Half connections never were "established", so do scan-checking here. 305 | # Scan::check_scan(c, F, F); 306 | # } 307 | # 308 | #event connection_rejected(c: connection) 309 | # { 310 | # local is_reverse_scan = (c$orig$state == TCP_RESET && c$id$resp_p !in likely_server_ports); 311 | # 312 | # Scan::check_scan(c, F, is_reverse_scan); 313 | # 314 | # #local trans = get_port_transport_proto(c$id$orig_p); 315 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 316 | # # TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 317 | # } 318 | # 319 | #event connection_reset(c: connection) 320 | # { 321 | # if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE ) 322 | # { 323 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 324 | # # We never heard from one side - that looks like a scan. 325 | # Scan::check_scan(c, c$orig$size + c$resp$size > 0, is_reverse_scan); 326 | # } 327 | # } 328 | # 329 | #event connection_pending(c: connection) 330 | # { 331 | # if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE ) 332 | # Scan::check_scan(c, F, F); 333 | # } 334 | -------------------------------------------------------------------------------- /scripts/check-knock.zeek: -------------------------------------------------------------------------------- 1 | # module to build network profile for scan detection 2 | # this module builds the 'ground-truth' ie prepares the list 3 | # legit LBNL servers and ports based on incoming SF. 4 | # premise: if external IP connecting to something not in this list 5 | # is likely a scan if (1) incoming connections meet fanout criteria 6 | 7 | # basically, the script works like this: 8 | # src: knock . 9 | # src: knock .. 10 | # src: knock ... 11 | # bro: bye-bye !!! 12 | 13 | # todo: 14 | # a. need backscatter identification (same src port diff dst port for scanner 15 | # b. address 80/tcp, 443/tcp, 861/tcp, 389/tcp (sticky config) - knock_high_threshold_ports 16 | # c. GeoIP integration - different treatment to > 130 miles IP vs US IPs vs Non-US IPs 17 | # d. False +ve suppression and statistics _ 18 | 19 | module Scan; 20 | 21 | #redef exit_only_after_terminate=F; 22 | 23 | export { 24 | global activate_KnockKnockScan = F &redef; 25 | 26 | redef enum Notice::Type += { 27 | KnockKnockScan, 28 | KnockKnockSummary, 29 | LikelyScanner, 30 | IgnoreLikelyScanner, 31 | KnockSummary, 32 | }; 33 | 34 | # sensitive and sticky config ports 35 | option knock_high_threshold_ports: set[port] = { 36 | 861/tcp, 37 | 80/tcp, 38 | #443/tcp, 39 | #8443/tcp, 40 | 8080/tcp 41 | } &redef; 42 | 43 | option knock_medium_threshold_ports: set[port] = { 44 | 17500/tcp, # dropbox-lan-sync 45 | 135/tcp, 46 | 139/tcp, 47 | 445/tcp, 48 | 0/tcp, 49 | 389/tcp, 50 | 88/tcp, 51 | 3268/tcp, 52 | 52311/tcp, 53 | } &redef; 54 | 55 | #redef knock_high_threshold_ports += { 113/tcp, 636/tcp, 135/tcp, 139/tcp, 17500/tcp, 18457/tcp, 56 | # 3268/tcp, 3389/tcp, 3832/tcp, 389/tcp, 57 | # 4242/tcp, 443/tcp, 445/tcp, 52311/tcp, 5900/tcp, 58 | # 60244/tcp, 60697/tcp, 80/tcp, 8080/tcp, 7000/tcp, 8192/tcp, 59 | # 8194/tcp, 8443/tcp, 88/tcp, 9001/tcp, 60 | # }; 61 | 62 | # scan candidate 63 | 64 | global expire_likely_scanner: function(t: table[addr, port] of set[addr], a: addr, p: port) 65 | : interval; 66 | global likely_scanner: table[addr, port] of set[addr] = table() &read_expire=1 day; # &synchronized ; 67 | 68 | global c_likely_scanner: table[addr, port] of opaque of cardinality 69 | &default=function(a: addr, p: port): opaque of cardinality { 70 | return hll_cardinality_init(0.1, 0.99); 71 | } &read_expire=1 day; 72 | 73 | global HIGH_THRESHOLD_LIMIT = 12 &redef; 74 | global MED_THRESHOLD_LIMIT = 5 &redef; 75 | global LOW_THRESHOLD_LIMIT = 3 &redef; 76 | 77 | global COMMUTE_DISTANCE = 320 &redef; 78 | 79 | # automated_exceptions using input-framework 80 | global ipportexclude_file = "/YURT/feeds/BRO-feeds/knockknock.exceptions" &redef; 81 | 82 | type ipportexclude_Idx: record { 83 | exclude_ip: addr; 84 | exclude_port: port &type_column="t"; 85 | }; 86 | 87 | type ipportexclude_Val: record { 88 | exclude_ip: addr; 89 | exclude_port: port &type_column="t"; 90 | comment: string &optional; 91 | }; 92 | 93 | global ipportexclude: table[addr, port] of ipportexclude_Val = table() &redef; # &synchronized ; 94 | #global concurrent_scanners_per_port: table[port] of set[addr] &write_expire=6 hrs ; # &synchronized ; 95 | 96 | # clusterization helper events 97 | global m_w_knockscan_add: event(orig: addr, d_port: port, resp: addr); 98 | global w_m_knockscan_new: event(orig: addr, d_port: port, resp: addr); 99 | global add_to_knockknock_cache: function(orig: addr, d_port: port, resp: addr); 100 | 101 | global check_knockknock_scan: function(orig: addr, d_port: port, resp: addr): bool; 102 | global check_KnockKnockScan: function(cid: conn_id, established: bool, 103 | reverse: bool): bool; 104 | 105 | global filterate_KnockKnockScan: function(c: connection, darknet: bool): string; 106 | } 107 | 108 | function expire_likely_scanner(t: table[addr, port] of set[addr], a: addr, p: port) 109 | : interval 110 | { 111 | log_reporter(fmt("expire_likely_scanner: %s, %s, %s", a, p, t[a, p]), 25); 112 | return 0 secs; 113 | } 114 | 115 | function check_knockknock_scan(orig: addr, d_port: port, resp: addr): bool 116 | { 117 | if ( gather_statistics ) 118 | s_counters$c_knock_core += 1; 119 | 120 | local result = F; 121 | 122 | local high_threshold_flag = F; 123 | local medium_threshold_flag = F; 124 | local usual_threshold_flag = F; 125 | local ultra_low_threshold_flag = F; 126 | 127 | # code and heuristics of to determine if orig is inface a scanner 128 | 129 | # gather geoip distance 130 | local orig_loc = lookup_location(orig); 131 | local resp_loc = lookup_location(resp); 132 | 133 | local distance = 0.0; 134 | distance = haversine_distance_ip(orig, resp); 135 | 136 | #print fmt ("%s, %s, %s", orig, d_port, resp); 137 | 138 | # We want to check for distributed scanners 139 | # basically flag scanners which remain below 140 | # block thresholds (esp happens in ipv6 world 141 | # we apply new heuristics: if a likely_scanner 142 | # is from a hotsubnet, we call it a scanner 143 | # this is sort of landmine version of knockknock 144 | 145 | 146 | local nnote: Notice::Type = KnockKnockScan; 147 | 148 | if (isHotSubnet(orig)) 149 | { 150 | #ultra_low_threshold_flag = T; 151 | #nnote = SubnetKnock; 152 | local a = 0 ; 153 | } 154 | 155 | # if driving distance, we raise the block threshold 156 | # 6 hours - covers tahoe and Yosemite from berkeley 157 | if ( distance < COMMUTE_DISTANCE ) { 158 | high_threshold_flag = T; 159 | } 160 | 161 | #if (d_port !in concurrent_scanners_per_port) 162 | #{ 163 | # concurrent_scanners_per_port[d_port]=set(); 164 | #} 165 | 166 | # stop populating the table if > 6 simultenious scanners 167 | # are probing on the same port. IN this case we 168 | # reduce the threshold to 3 faolures to block 169 | #if (|concurrent_scanners_per_port[d_port]| <=5) 170 | #{ 171 | # add concurrent_scanners_per_port[d_port][orig] ; 172 | #} 173 | 174 | local flux_density = check_port_flux_density(d_port, orig); 175 | 176 | # check if in knock_high_threshold_ports or rare port scan (too few concurrent scanners) 177 | # notch up threshold ot high - likewise for medium thresholds 178 | 179 | #if (! high_threshold_flag ) 180 | #{ 181 | # if (d_port in knock_high_threshold_ports || |concurrent_scanners_per_port[d_port]| <=2) 182 | # { high_threshold_flag = T ; } 183 | # else if (d_port in knock_medium_threshold_ports || |concurrent_scanners_per_port[d_port]| <=5) 184 | # { medium_threshold_flag = T ; } 185 | #} 186 | 187 | if ( ! high_threshold_flag ) { 188 | if ( d_port in knock_high_threshold_ports || flux_density <= 2 ) { 189 | high_threshold_flag = T; 190 | } else if ( d_port in knock_medium_threshold_ports || flux_density <= 5 ) { 191 | medium_threshold_flag = T; 192 | } 193 | } 194 | 195 | local d_val = 0; 196 | 197 | if ( orig !in Scan::known_scanners ) { 198 | if ( enable_big_tables ) { 199 | d_val = |likely_scanner[orig, d_port]|; 200 | } else { 201 | d_val = double_to_count(hll_cardinality_estimate(c_likely_scanner[orig, 202 | d_port])); 203 | } 204 | 205 | if ( d_val == HIGH_THRESHOLD_LIMIT && high_threshold_flag ) { 206 | result = T; 207 | } else if ( d_val == MED_THRESHOLD_LIMIT && medium_threshold_flag ) { 208 | result = T; 209 | } else if ( d_val >= LOW_THRESHOLD_LIMIT 210 | && ! high_threshold_flag 211 | && ! medium_threshold_flag ) { 212 | result = T; 213 | } 214 | } 215 | 216 | if (ultra_low_threshold_flag) 217 | result = T; 218 | 219 | if ( result ) { 220 | # make sure there is country code 221 | local cc = orig_loc?$country_code ? orig_loc$country_code : ""; 222 | 223 | #local _msg = fmt("%s scanned a total of %d hosts: [%s] (port-flux-density: %s) (origin: %s distance: %.2f miles)", orig, d_val,d_port, |concurrent_scanners_per_port[d_port]|, cc, distance); 224 | local _msg = fmt("%s scanned a total of %d hosts: [%s] (port-flux-density: %s) (origin: %s distance: %.2f miles)", orig, d_val, d_port, 225 | flux_density, cc, distance); 226 | 227 | #$ts=current_time(), 228 | NOTICE([$note=nnote, $src=orig, $p=d_port, $msg=fmt("%s", _msg)]); 229 | 230 | } 231 | #log_reporter (fmt ("NOTICE: FOUND %s: %s on %s", nnote, orig, Cluster::node),0); 232 | return result; 233 | } 234 | 235 | #check_knockknock_scan: 222.85.138.75, 3128/tcp, 131.243.192.47, 1463019177.63643, 1463019216.164206 - DETECTED 236 | #1463019177.636430 error in ./.././check-knock.bro, line 199: value used but not set (Scan::add_to_known_scanners) 237 | #1463019177.636430 error in ./.././check-knock.bro, line 250: no such index (Scan::known_scanners[Scan::orig]) 238 | #1463019177.636430 error in ./.././check-knock.bro, line 251: no such index (Scan::known_scanners[Scan::orig]) 239 | 240 | function check_KnockKnockScan(cid: conn_id, established: bool, reverse: bool): bool 241 | { 242 | local result = F; 243 | 244 | if ( gather_statistics ) 245 | s_counters$c_knock_checkscan += 1; 246 | 247 | # already filterated connection 248 | local orig = cid$orig_h; 249 | local resp = cid$resp_h; 250 | local d_port = cid$resp_p; 251 | 252 | #already identified as scanner no need to proceed further 253 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) 254 | return F; 255 | 256 | # only worry about TCP connections 257 | # we deal with udp and icmp scanners differently 258 | 259 | # aashish UDP 260 | if ( get_port_transport_proto(cid$resp_p) != tcp ) 261 | return F; 262 | 263 | # memory optimizations 264 | if ( enable_big_tables ) { 265 | if ( [orig, d_port] !in likely_scanner ) { 266 | likely_scanner[orig, d_port] = set(); 267 | } 268 | 269 | if ( resp !in likely_scanner[orig, d_port] ) { 270 | add likely_scanner[orig, d_port][resp]; 271 | } 272 | } else { 273 | if ( [orig, d_port] !in c_likely_scanner ) { 274 | local cp: opaque of cardinality = hll_cardinality_init(0.1, 0.99); 275 | c_likely_scanner[orig, d_port] = cp; 276 | } 277 | 278 | hll_cardinality_add(c_likely_scanner[orig, d_port], resp); 279 | } 280 | 281 | result = check_knockknock_scan(orig, d_port, resp); 282 | 283 | return result; 284 | } 285 | 286 | @if ( ! Cluster::is_enabled() ) 287 | event connection_state_remove(c: connection) 288 | { 289 | local darknet = F; 290 | # check_KnockKnockScan(c$id, F, F) ; 291 | } 292 | @endif 293 | 294 | function filterate_KnockKnockScan(c: connection, darknet: bool): string 295 | { 296 | if ( gather_statistics ) 297 | s_counters$c_knock_filterate += 1; 298 | 299 | if ( ! activate_KnockKnockScan ) 300 | return ""; 301 | 302 | local orig = c$id$orig_h; 303 | local resp = c$id$resp_h; 304 | local d_port = c$id$resp_p; 305 | local s_port = c$id$orig_p; 306 | 307 | # internal host scanning handled seperately 308 | if ( Site::is_local_addr(c$id$orig_h) ) 309 | return ""; 310 | 311 | if ( /\^/ in c$history) 312 | return "" ; 313 | 314 | #local darknet = Scan::is_darknet(c$id$resp_h); 315 | 316 | if ( ! darknet ) { 317 | # only worry about TCP connections 318 | # we deal with udp and icmp scanners differently 319 | if ( get_port_transport_proto(c$id$resp_p) != tcp ) 320 | return ""; 321 | 322 | # a) full established conns not interesting 323 | if ( c$resp$state == TCP_ESTABLISHED ) { 324 | return ""; 325 | } 326 | 327 | # b) full established conns not interesting 328 | if ( c?$conn && c$conn?$conn_state ) { 329 | if ( /SF/ in c$conn$conn_state ) { 330 | return ""; 331 | } 332 | 333 | local state = c$conn$conn_state; 334 | local resp_bytes = c$resp$size; 335 | 336 | # mid stream traffic - ignore 337 | if ( state == "OTH" && resp_bytes > 0 ) { 338 | return ""; 339 | } 340 | } 341 | } 342 | 343 | # ignore traffic to host/port this is primarily whitelisting 344 | # maintained in ipportexclude_file for sticky config firewalled hosts 345 | if ( [resp, d_port] in ipportexclude ) { 346 | return ""; 347 | } 348 | 349 | # if ever a SF a LBL host on this port - ignore the orig completely 350 | if ( resp in Site::host_profiles && d_port in Site::host_profiles[resp] ) 351 | return ""; 352 | 353 | # don't need to process known_scanners again 354 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) { 355 | log_reporter(fmt("check_KnockKnockScan: orig in known_scanner"), 0); 356 | return ""; 357 | } 358 | 359 | # finally a scan candidate 360 | return "K"; 361 | #add_to_knockknock_cache(orig, d_port, resp); 362 | } 363 | 364 | event zeek_init() 365 | { 366 | Input::add_table([ 367 | $source=ipportexclude_file, 368 | $name="ipportexclude", 369 | $idx=ipportexclude_Idx, 370 | $val=ipportexclude_Val, 371 | $destination=ipportexclude, 372 | $mode=Input::REREAD]); 373 | } 374 | -------------------------------------------------------------------------------- /scripts/check-knocksubnet.zeek: -------------------------------------------------------------------------------- 1 | # module to rely on the fact that multiple 2 | # scanners are often coming from same /24 or /64 3 | # so just knock them on the very first connection 4 | # once confidence is hight that subnet is bad 5 | 6 | module Scan; 7 | 8 | export { 9 | global activate_SubnetKnock = F &redef; 10 | 11 | redef enum Notice::Type += { 12 | SubnetKnock, 13 | }; 14 | 15 | global Scan::filterate_SubnetKnock: function(c: connection, darknet: bool): string; 16 | global Scan::check_SubnetKnock: function(cid: conn_id, established: bool, reverse: bool): bool; 17 | } 18 | 19 | function Scan::filterate_SubnetKnock(c: connection, darknet: bool): string 20 | { 21 | local rvalue =""; 22 | 23 | if (c$id$orig_h in hs) 24 | rvalue = "S"; 25 | 26 | if ( Scan::isHotSubnet(c$id$orig_h) ) 27 | rvalue = "S" ; 28 | 29 | return rvalue ; 30 | } 31 | 32 | function Scan::check_SubnetKnock(cid: conn_id, established: bool, reverse: bool): bool 33 | { 34 | local result = F; 35 | local orig = cid$orig_h; 36 | local dport = cid$resp_p; 37 | 38 | local orig_loc = lookup_location(orig); 39 | local flux_density = check_port_flux_density(dport, orig); 40 | local distance = 0.0; 41 | distance = haversine_distance_ip(orig, cid$resp_h); 42 | 43 | local s = get_subnet(orig); 44 | 45 | if ( isHotSubnet(orig) ) 46 | { 47 | # make sure there is country code 48 | local cc = orig_loc?$country_code ? orig_loc$country_code : ""; 49 | 50 | local _msg = fmt("%s scanned a total of 1 hosts %s on [%s] (HotSubnet: %s, port-flux-density: %s) (origin: %s distance: %.2f miles)", orig, cid$resp_h, 51 | cid$resp_p, s, flux_density, cc, distance); 52 | 53 | NOTICE([$note=SubnetKnock, $src=orig, $p=cid$resp_p, $msg=fmt("%s", _msg)]); 54 | return T; 55 | } 56 | 57 | return F; 58 | } 59 | -------------------------------------------------------------------------------- /scripts/check-landmine.zeek: -------------------------------------------------------------------------------- 1 | # extenti/n of bro-1.5.3 landmine and bro-2.x darknet.bro 2 | 3 | module Scan; 4 | 5 | @load base/protocols/conn 6 | #@load site-subnets.bro 7 | 8 | export { 9 | global activate_LandMine = F &redef; 10 | 11 | const landmine_thresh_trigger = 5 &redef; 12 | const landmine_address: set[addr] &redef; 13 | 14 | redef enum Notice::Type += { 15 | LandMine, # source touched a landmine destination 16 | LandMineSummary, # aggregate of landmine scanner 17 | }; 18 | 19 | global landmine_scan_summary: function(t: table[addr] of set[addr], orig: addr) 20 | : interval; 21 | 22 | global landmine_distinct_peers: table[addr] of set[addr] &read_expire=1 day 23 | &expire_func=landmine_scan_summary &redef; 24 | 25 | # Expire functions that trigger summaries. 26 | global c_landmine_scan_summary: function(t: table[addr] of opaque of 27 | cardinality, orig: addr): interval; 28 | 29 | global c_landmine_distinct_peers: table[addr] of opaque of cardinality 30 | &default=function(n: any): opaque of cardinality { 31 | return hll_cardinality_init(0.1, 0.99); 32 | } &read_expire=1 days &expire_func=c_landmine_scan_summary; 33 | 34 | global landmine_ignore_ports: set[port] = { 35 | 53/tcp, 36 | 53/udp 37 | } &redef; 38 | 39 | # atleast these many subnets in the active subnets table 40 | const MIN_SUBNET_CHECK = 1; 41 | 42 | global check_LandMine: function(cid: conn_id, established: bool, 43 | reversed: bool): bool; 44 | global filterate_LandMineScan: function(c: connection, darknet: bool): string; 45 | 46 | const allow_icmp_landmine_check: bool = F &redef; 47 | global ignore_landmine_ports: set[port] = { } &redef; 48 | } 49 | 50 | function landmine_scan_summary(t: table[addr] of set[addr], orig: addr) 51 | : interval 52 | { 53 | #log_reporter(fmt("landmine_scan_summary: %s", |t[orig]|), 0); 54 | return 0 secs; 55 | } 56 | 57 | function check_LandMine(cid: conn_id, established: bool, reversed: bool): bool 58 | { 59 | if ( gather_statistics ) 60 | s_counters$c_land_checkscan += 1; 61 | 62 | local orig = cid$orig_h; 63 | local resp = cid$resp_h; 64 | local d_port = cid$resp_p; 65 | 66 | if ( orig in known_scanners && Scan::known_scanners[orig]$status ) { 67 | return F; 68 | } 69 | 70 | if ( resp in Site::local_nets && resp in Scan::subnet_table ) { 71 | log_reporter(fmt("PROBLEM - IP in site_subnet_table: %s", cid), 5); 72 | return F; 73 | } 74 | 75 | if ( [orig] !in landmine_distinct_peers ) 76 | landmine_distinct_peers[orig] = set(); 77 | 78 | if ( [resp] !in landmine_distinct_peers[orig] ) { 79 | add landmine_distinct_peers[orig][resp]; 80 | 81 | if ( |landmine_distinct_peers[orig]| >= landmine_thresh_trigger ) { 82 | local iplist = ""; 83 | 84 | for ( ip in landmine_distinct_peers[orig] ) { 85 | iplist += fmt(" %s", ip); 86 | } 87 | 88 | local msg = fmt("landmine address trigger %s [%s] %s", orig, d_port, iplist); 89 | NOTICE([$note=LandMine, $src=orig, $p=d_port, $id=cid, $msg=msg]); 90 | return T; 91 | } 92 | } 93 | 94 | # if ([orig] !in c_landmine_distinct_peers) 95 | # { 96 | # local cp: opaque of cardinality = hll_cardinality_init(0.1, 0.99); 97 | # c_landmine_distinct_peers[orig]=cp ; 98 | # } 99 | # hll_cardinality_add(c_landmine_distinct_peers[orig], resp); 100 | # 101 | # # local result = check_landmine_scan(orig, d_port, resp) ; 102 | # 103 | # local d_val = double_to_count(hll_cardinality_estimate(c_landmine_distinct_peers[orig])) ; 104 | # 105 | # if (d_val >= landmine_thresh_trigger) 106 | # { 107 | # local msg=fmt ("Landmine hit by %s", orig); 108 | # NOTICE([$note=LandMine, $src=orig, $src_peer=get_local_event_peer(), $msg=msg]); 109 | # log_reporter (fmt ("NOTICE: FOUND LandMine : %s, %s", orig, network_time()),0); 110 | # return T ; 111 | # } 112 | 113 | return F; 114 | } 115 | 116 | function filterate_LandMineScan(c: connection, darknet: bool): string 117 | { 118 | if ( gather_statistics ) 119 | s_counters$c_land_filterate += 1; 120 | 121 | local orig = c$id$orig_h; 122 | local resp = c$id$resp_h; 123 | 124 | local orig_p = c$id$orig_p; 125 | local resp_p = c$id$resp_p; 126 | 127 | # check if src is local_nets, ignore for the time-being 128 | if ( orig in Site::local_nets ) 129 | return ""; 130 | 131 | # check if really Darknet IP or not 132 | if ( resp in Site::local_nets && resp in Scan::subnet_table ) 133 | return ""; 134 | 135 | # just in case 136 | if ( ! darknet ) 137 | return ""; 138 | 139 | # prevent manager to keep firing events if already a scanner 140 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) { 141 | #local rmsg = fmt ("landmine: known_scanner T: for %s, %s, %s", orig, resp_p, resp); 142 | #log_reporter(rmsg, 0); 143 | return ""; 144 | } 145 | 146 | if ( /\^h/ in c$history ) 147 | return ""; 148 | 149 | if ( allow_icmp_landmine_check ) { 150 | if ( get_port_transport_proto(c$id$resp_p) == icmp 151 | && c$id$resp_p in ignore_landmine_ports ) 152 | return ""; 153 | } else if ( get_port_transport_proto(c$id$resp_p) == icmp ) 154 | return ""; 155 | 156 | # limitation - works good only for tcp|icmp with minimal false positive 157 | # for udp see - udp-scan.bro 158 | # enabling udp LandMine - 2019-09-16 aashish 159 | #if (get_port_transport_proto(c$id$resp_p) != tcp ) 160 | # return ""; 161 | 162 | if ( resp_p in Scan::landmine_ignore_ports ) 163 | return ""; 164 | 165 | # min membership check if subnets-txt file is loaded 166 | # TODO: raise an alarm or take corrective actions 167 | # right now made failsafe - atleast 1 subnet needed 168 | 169 | if ( |Scan::subnet_table| < MIN_SUBNET_CHECK ) { 170 | #local msg = fmt("Scan::subnet_table is %d size which is below threshold. Deactivating LandMine Check", |Scan::subnet_table|); 171 | #event reporter_info(network_time(), msg, peer_description); 172 | return ""; 173 | } 174 | 175 | # if (orig in distinct_backscatter_peers) 176 | # if (orig_p in distinct_backscatter_peers[orig]) 177 | # if (|distinct_backscatter_peers[orig][orig_p]| < 2) 178 | # return "" ; 179 | 180 | if ( Site::is_local_addr(resp) && resp !in Scan::subnet_table ) { 181 | #if ((is_failed(c) || is_reverse_failed(c) ) ) 182 | return "L"; 183 | } 184 | 185 | return ""; 186 | } 187 | # TODO: for future - add liveNet identification for in case subnet or parts of subnet from darknet 188 | # wakes up 189 | 190 | #@if ( ! Cluster::is_enabled()) 191 | #event connection_state_remove(c: connection) 192 | #{ 193 | # filterate_LandMineScan(c, T); 194 | #} 195 | #@endif 196 | 197 | #const TCP_INACTIVE = 0; #< Endpoint is still inactive. 198 | #const TCP_SYN_SENT = 1; #< Endpoint has sent SYN. 199 | #const TCP_SYN_ACK_SENT = 2; #< Endpoint has sent SYN/ACK. 200 | #const TCP_PARTIAL = 3; #< Endpoint has sent data but no initial SYN. 201 | #const TCP_ESTABLISHED = 4; #< Endpoint has finished initial handshake regularly. 202 | #const TCP_CLOSED = 5; #< Endpoint has closed connection. 203 | #const TCP_RESET = 6; #< Endpoint has sent RST. 204 | 205 | # UDP values for :bro:see:`endpoint` *state* field. 206 | # todo:: these should go into an enum to make them autodoc'able. 207 | #const UDP_INACTIVE = 0; #< Endpoint is still inactive. 208 | #const UDP_ACTIVE = 1; #< Endpoint has sent something. 209 | 210 | # vern's original landline detector from 1.5.3 era 211 | # if ( activate_landmine_check && 212 | # n >= landmine_thresh_trigger && 213 | # mask_addr(resp, 24) in landmine_address ) 214 | # { 215 | # local msg2 = fmt("landmine address trigger %s%s ", orig, svc); 216 | # NOTICE([$note=LandMine, $src=orig, 217 | # $p=service, $msg=msg2]); 218 | # } 219 | -------------------------------------------------------------------------------- /scripts/check-lowporttroll.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | const activate_LowPortTrolling = T &redef; 5 | 6 | # Ignore address scanners for further scan detection after 7 | # scanning this many hosts. 8 | # 0 disables. 9 | #const ignore_scanners_threshold = 0 &redef; 10 | 11 | redef enum Notice::Type += { 12 | LowPortTrolling, # source touched privileged ports 13 | LowPortScanSummary, # summary of distinct low ports per scanner 14 | }; 15 | 16 | global lowport_summary: function(t: table[addr] of set[port], orig: addr) 17 | : interval; 18 | global distinct_low_ports: table[addr] of set[port] &read_expire=1 days 19 | &expire_func=lowport_summary &redef; 20 | 21 | const lowport_summary_trigger = 10 &redef; 22 | 23 | # Threshold for scanning privileged ports. 24 | const priv_scan_trigger = 5 &redef; 25 | const troll_skip_service = { 26 | 25/tcp, 27 | 21/tcp, 28 | 22/tcp, 29 | 20/tcp, 30 | 80/tcp, 31 | 443/tcp, 32 | 2049/tcp, 33 | 2049/udp, 34 | } &redef; 35 | 36 | global filterate_LowPortTroll: function(c: connection, established: bool, 37 | reverse: bool): string; 38 | } 39 | 40 | function lowport_summary(t: table[addr] of set[port], orig: addr): interval 41 | { 42 | local num_distinct_lowports = orig in t ? |t[orig]| : 0; 43 | 44 | if ( num_distinct_lowports >= lowport_summary_trigger ) 45 | NOTICE([ 46 | $note=LowPortScanSummary, 47 | $src=orig, 48 | $n=num_distinct_lowports, 49 | $msg=fmt("%s scanned a total of %d low ports", orig, num_distinct_lowports)]); 50 | 51 | return 0 secs; 52 | } 53 | 54 | #orig: addr, service: port, resp: addr): bool 55 | function check_LowPortTroll(cid: conn_id, established: bool, reverse: bool): bool 56 | { 57 | local id = cid; 58 | 59 | local service = reverse ? id$orig_p : id$resp_p; 60 | local rev_service = reverse ? id$resp_p : id$orig_p; 61 | local orig = reverse ? id$resp_h : id$orig_h; 62 | local resp = reverse ? id$orig_h : id$resp_h; 63 | local outbound = Site::is_local_addr(orig); 64 | 65 | local troll = F; 66 | if ( orig !in distinct_low_ports || service !in distinct_low_ports[orig] ) { 67 | if ( orig !in distinct_low_ports ) 68 | distinct_low_ports[orig] = set(); 69 | 70 | add distinct_low_ports[orig][service]; 71 | 72 | if ( |distinct_low_ports[orig]| == priv_scan_trigger 73 | && orig !in Site::neighbor_nets ) { 74 | #local s = service in port_names ? port_names[service] : fmt("%s", service); 75 | local s = fmt("%s", service); 76 | 77 | local svrc_msg = fmt("low port trolling %s %s", orig, s); 78 | NOTICE([ 79 | $note=LowPortTrolling, 80 | $src=orig, 81 | $p=service, 82 | $msg=svrc_msg]); 83 | 84 | troll = T; 85 | } 86 | } 87 | 88 | return troll; 89 | } 90 | 91 | function filterate_LowPortTroll(c: connection, established: bool, reverse: bool): string 92 | { 93 | if ( established ) { 94 | # Don't consider established connections for port scanning, 95 | # it's too easy to be mislead by FTP-like applications that 96 | # legitimately gobble their way through the port space. 97 | return ""; 98 | } 99 | 100 | local id = c$id; 101 | 102 | local service = "ftp-data" in c$service ? 20/tcp : ( reverse ? id$orig_p : 103 | id$resp_p ); 104 | local rev_service = reverse ? id$resp_p : id$orig_p; 105 | local orig = reverse ? id$resp_h : id$orig_h; 106 | local resp = reverse ? id$orig_h : id$resp_h; 107 | local outbound = Site::is_local_addr(orig); 108 | 109 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) 110 | return ""; 111 | 112 | if ( orig in Site::neighbor_nets ) 113 | return ""; 114 | 115 | # Check for low ports. 116 | if ( activate_LowPortTrolling 117 | && ! outbound 118 | && service < 1024/tcp 119 | && service !in troll_skip_service ) { 120 | return "T"; 121 | # local troll_result = check_lowporttrolling(orig, service, resp); 122 | } 123 | 124 | return ""; 125 | } 126 | # events for scan detections 127 | 128 | #event connection_established(c: connection) 129 | # { 130 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 131 | # Scan::check_LowPortTroll(c, T, is_reverse_scan); 132 | # 133 | # #local trans = get_port_transport_proto(c$id$orig_p); 134 | # #if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 135 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 136 | # } 137 | # 138 | #event partial_connection(c: connection) 139 | # { 140 | # Scan::check_LowPortTroll(c, T, F); 141 | # } 142 | # 143 | #event connection_attempt(c: connection) 144 | # { 145 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 146 | # Scan::check_LowPortTroll(c, F, is_reverse_scan); 147 | # 148 | # #local trans = get_port_transport_proto(c$id$orig_p); 149 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 150 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 151 | # } 152 | # 153 | #event connection_half_finished(c: connection) 154 | # { 155 | # # Half connections never were "established", so do scan-checking here. 156 | # Scan::check_LowPortTroll(c, F, F); 157 | # } 158 | # 159 | #event connection_rejected(c: connection) 160 | # { 161 | # local is_reverse_scan = (c$orig$state == TCP_RESET && c$id$resp_p !in likely_server_ports); 162 | # 163 | # Scan::check_LowPortTroll(c, F, is_reverse_scan); 164 | # 165 | # #local trans = get_port_transport_proto(c$id$orig_p); 166 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 167 | # # TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 168 | # } 169 | # 170 | #event connection_reset(c: connection) 171 | # { 172 | # if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE ) 173 | # { 174 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 175 | # # We never heard from one side - that looks like a scan. 176 | # Scan::check_LowPortTroll(c, c$orig$size + c$resp$size > 0, is_reverse_scan); 177 | # } 178 | # } 179 | # 180 | #event connection_pending(c: connection) 181 | # { 182 | # if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE ) 183 | # Scan::check_LowPortTroll(c, F, F); 184 | # } 185 | # 186 | # 187 | -------------------------------------------------------------------------------- /scripts/check-port-knock.zeek: -------------------------------------------------------------------------------- 1 | # module to build network profile for scan detection 2 | # this module builds the 'ground-truth' ie prepares the list 3 | # legit LBNL servers and ports based on incoming SF. 4 | # premise: if external IP connecting to something not in this list 5 | # is likely a scan if (1) incoming connections meet fanout criteria 6 | 7 | # basically, the script works like this: 8 | # src: knock . 9 | # src: knock .. 10 | # src: knock ... 11 | # bro: bye-bye !!! 12 | 13 | # todo: 14 | # a. need backscatter identification (same src port diff dst port for scanner 15 | # b. address 80/tcp, 443/tcp, 861/tcp, 389/tcp (sticky config) - knock_high_threshold_ports 16 | # c. GeoIP integration - different treatment to > 130 miles IP vs US IPs vs Non-US IPs 17 | # d. False +ve suppression and statistics _ 18 | 19 | module Scan; 20 | 21 | #redef exit_only_after_terminate=F; 22 | 23 | export { 24 | global activate_KnockKnockPort = T &redef; 25 | 26 | redef enum Notice::Type += { 27 | KnockKnockPort, # source flagged as scanner by TRW algorithm 28 | KnockKnockSummary, # summary of scanning activities reported by TRW 29 | LikelyScanner, 30 | IgnoreLikelyScanner, 31 | KnockSummary, 32 | }; 33 | 34 | # sensitive and sticky config ports 35 | 36 | global high_sensitivity_ports: port = 33000/tcp; 37 | 38 | global scanner_default_ports: set[port] &redef; 39 | 40 | global hot_ports: set[port] &redef; 41 | 42 | redef hot_ports += { 43 | 23/tcp, 44 | 80/tcp, 45 | 443/tcp, 46 | 1433/tcp, 47 | 22/tcp, 48 | 6379/tcp, 49 | 445/tcp, 50 | 0/tcp, 51 | 4028/tcp, 52 | 3/tcp, 53 | 3389/tcp, 54 | 3306/tcp, 55 | 25/tcp, 56 | 8080/tcp, 57 | 9200/tcp, 58 | 53/tcp, 59 | 861/tcp, 60 | 49152/tcp, 61 | 11720/tcp, 62 | 20547/tcp, 63 | 2082/tcp, 64 | 5060/tcp, 65 | 9999/tcp, 66 | 444/tcp, 67 | 3392/tcp, 68 | 3391/tcp, 69 | 27017/tcp, 70 | 4444/tcp, 71 | 3128/tcp, 72 | 4899/tcp, 73 | 5900/tcp, 74 | 8128/tcp, 75 | 11211/tcp, 76 | 1/tcp, 77 | 8090/tcp, 78 | 8888/tcp, 79 | 21/tcp, 80 | 2222/tcp, 81 | 110/tcp, 82 | 47808/tcp, 83 | 554/tcp, 84 | 3393/tcp, 85 | 17/tcp, 86 | 10001/tcp, 87 | 1093/tcp, 88 | 8081/tcp, 89 | 81/tcp, 90 | 8088/tcp, 91 | 102/tcp, 92 | 8123/tcp, 93 | 13/tcp, 94 | 3390/tcp, 95 | 10022/tcp, 96 | 3386/tcp, 97 | 40876/tcp, 98 | 40884/tcp, 99 | 10/tcp, 100 | 1080/tcp, 101 | 1755/tcp, 102 | 161/tcp, 103 | 3388/tcp, 104 | 3394/tcp, 105 | 3395/tcp, 106 | 9100/tcp, 107 | 6881/tcp, 108 | 5901/tcp, 109 | 3385/tcp, 110 | 3384/tcp, 111 | 3387/tcp, 112 | 873/tcp, 113 | 139/tcp, 114 | 9600/tcp, 115 | 7777/tcp, 116 | 84/tcp, 117 | 91/tcp, 118 | 82/tcp, 119 | 6666/tcp, 120 | 502/tcp, 121 | 902/tcp, 122 | 27015/tcp, 123 | 53413/tcp, 124 | 32764/tcp, 125 | 44818/tcp, 126 | 88/tcp, 127 | 8000/tcp, 128 | 5038/tcp, 129 | 1604/tcp, 130 | 137/tcp, 131 | 8082/tcp, 132 | 87/tcp, 133 | 5904/tcp, 134 | 85/tcp, 135 | 86/tcp, 136 | 8/tcp, 137 | 843/tcp, 138 | 389/tcp, 139 | 5222/tcp, 140 | 5902/tcp, 141 | 993/tcp, 142 | 49153/tcp 143 | }; 144 | 145 | # scan candidate 146 | global likely_port_scanner: table[addr, addr] of set[port] &read_expire=1 day; # &synchronized ; 147 | 148 | #global c_likely_port_scanner: table[addr,port] of opaque of cardinality 149 | # &default = function(n: any): opaque of cardinality { return hll_cardinality_init(0.1, 0.99); } 150 | # &read_expire=1 day ; 151 | 152 | global HIGH_THRESHOLD_LIMIT = 30 &redef; 153 | global MED_THRESHOLD_LIMIT = 12 &redef; 154 | global LOW_THRESHOLD_LIMIT = 3 &redef; 155 | 156 | global COMMUTE_DISTANCE = 320 &redef; 157 | 158 | global check_knockknock_port: function(orig: addr, d_port: port, resp: addr): bool; 159 | global check_KnockKnockPort: function(cid: conn_id, established: bool, 160 | reverse: bool): bool; 161 | global filterate_KnockKnockPort: function(c: connection, darknet: bool): string; 162 | 163 | global c_concurrent_scanners_per_port: table[port] of opaque of cardinality 164 | &default=function(n: any): opaque of cardinality { 165 | return hll_cardinality_init(0.1, 0.99); 166 | } &read_expire=1 day; 167 | } 168 | 169 | function check_knockknock_port(orig: addr, d_port: port, resp: addr): bool 170 | { 171 | local result = F; 172 | 173 | local high_threshold_flag = F; 174 | local medium_threshold_flag = F; 175 | local low_threshold_flag = F; 176 | 177 | # # # # # # # # # # # 178 | # code and heuristics of to determine if orig is inface a scanner 179 | 180 | # gather geoip distance 181 | local orig_loc = lookup_location(orig); 182 | local resp_loc = lookup_location(resp); 183 | 184 | local distance = 0.0; 185 | #distance = get_haversine_distance(orig, resp); 186 | 187 | # if driving distance, we raise the block threshold 188 | # 6 hours - covers tahoe and Yosemite from berkeley 189 | if ( distance < COMMUTE_DISTANCE ) { 190 | high_threshold_flag = F; 191 | } 192 | 193 | if ( d_port !in c_concurrent_scanners_per_port ) { 194 | local cp: opaque of cardinality = hll_cardinality_init(0.1, 0.99); 195 | c_concurrent_scanners_per_port[d_port] = cp; 196 | } 197 | 198 | hll_cardinality_add(c_concurrent_scanners_per_port[d_port], orig); 199 | 200 | local d_val = double_to_count(hll_cardinality_estimate( 201 | c_concurrent_scanners_per_port[d_port])); 202 | 203 | # check if in knock_high_threshold_ports or rare port scan (too few concurrent scanners) 204 | # notch up threshold ot high - likewise for medium thresholds 205 | 206 | if ( d_port > high_sensitivity_ports || d_val >= 10 ) { 207 | high_threshold_flag = T; 208 | } else if ( d_port in hot_ports || d_val >= 5 ) { 209 | low_threshold_flag = T; 210 | } 211 | 212 | if ( orig !in Scan::known_scanners ) { 213 | if ( |likely_port_scanner[orig, resp]| == HIGH_THRESHOLD_LIMIT 214 | && high_threshold_flag ) { 215 | result = T; 216 | } else if ( |likely_port_scanner[orig, resp]| == MED_THRESHOLD_LIMIT 217 | && medium_threshold_flag ) { 218 | result = T; 219 | } #else if (|likely_port_scanner[orig,resp]| >= LOW_THRESHOLD_LIMIT && !high_threshold_flag && !medium_threshold_flag) 220 | else if ( |likely_port_scanner[orig, resp]| >= LOW_THRESHOLD_LIMIT 221 | && low_threshold_flag ) { 222 | result = T; 223 | } 224 | } 225 | 226 | if ( result ) { 227 | # make sure there is country code 228 | local cc = orig_loc?$country_code ? orig_loc$country_code : ""; 229 | 230 | # build list of hosts touched 231 | 232 | local hosts_probed = ""; 233 | for ( a in likely_port_scanner[orig, resp] ) 234 | hosts_probed += fmt(" %s ", a); 235 | 236 | local _msg = fmt("%s scanned a total of %d ports on %s: (origin: %s distance: %.2f miles) on %s", orig, 237 | |likely_port_scanner[orig, resp]|, resp, cc, distance, 238 | hosts_probed); 239 | NOTICE([$note=KnockKnockPort, $src=orig, $msg=fmt("%s", _msg)]); 240 | log_reporter(fmt("check_knockknock_scan: %s, %s, %s, %s, %s - DETECTED", orig, d_port, resp, 241 | network_time(), current_time()), 1); 242 | 243 | #Scan::add_to_known_scanners(orig, "KnockKnockPort"); 244 | } 245 | # # # # # # # # # # # 246 | return result; 247 | } 248 | 249 | function check_KnockKnockPort(cid: conn_id, established: bool, reverse: bool): bool 250 | { 251 | # already filterated connection 252 | 253 | local orig = cid$orig_h; 254 | local resp = cid$resp_h; 255 | local d_port = cid$resp_p; 256 | 257 | #already identified as scanner no need to proceed further 258 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) 259 | return F; 260 | 261 | # only worry about TCP connections 262 | # we deal with udp and icmp scanners differently 263 | 264 | if ( get_port_transport_proto(cid$resp_p) != tcp ) 265 | return F; 266 | 267 | if ( [orig, resp] !in likely_port_scanner ) { 268 | likely_port_scanner[orig, resp] = set(); 269 | } 270 | 271 | if ( d_port !in likely_port_scanner[orig, resp] ) { 272 | add likely_port_scanner[orig, resp][d_port]; 273 | } 274 | 275 | local result = check_knockknock_port(orig, d_port, resp); 276 | 277 | # TODO: this should go down further into check-scan-impl.bro code 278 | if ( result ) { 279 | # Important want ot make sure we update the detect_ts to nearest time of occurence 280 | #Scan::known_scanners[orig]$detect_ts = network_time(); 281 | #log_reporter(fmt("knockknock port scanner detected at %s, %s on %s", orig, Scan::known_scanners[orig]$detect_ts, peer_description),0); 282 | 283 | return T; 284 | } 285 | 286 | return F; 287 | } 288 | 289 | # clusterizations 290 | 291 | event udp_request(u: connection) 292 | { } 293 | 294 | event udp_reply(u: connection) 295 | { } 296 | 297 | event connection_state_remove(c: connection) 298 | { 299 | local darknet = F; 300 | 301 | if ( /PortKnock/ in filterate_KnockKnockPort(c, F) ) { 302 | if ( check_KnockKnockPort(c$id, F, F) ) { 303 | log_reporter(fmt("connection_state_remove: w_m_update_scanner: %s", 304 | known_scanners[c$id$orig_h]), 0); 305 | event Scan::w_m_update_scanner(known_scanners[c$id$orig_h]); 306 | } 307 | } 308 | } 309 | 310 | function filterate_KnockKnockPort(c: connection, darknet: bool): string 311 | { 312 | if ( ! activate_KnockKnockPort ) 313 | return ""; 314 | 315 | local orig = c$id$orig_h; 316 | local resp = c$id$resp_h; 317 | local d_port = c$id$resp_p; 318 | local s_port = c$id$orig_p; 319 | 320 | # internal host scanning handled seperately 321 | if ( Site::is_local_addr(c$id$orig_h) ) 322 | return ""; 323 | 324 | #local darknet = Scan::is_darknet(c$id$resp_h); 325 | 326 | # only worry about TCP connections 327 | # we deal with udp and icmp scanners differently 328 | if ( get_port_transport_proto(c$id$resp_p) != tcp ) 329 | return ""; 330 | 331 | # a) full established conns not interesting 332 | if ( c$resp$state == TCP_ESTABLISHED ) { 333 | return ""; 334 | } 335 | 336 | # b) full established conns not interesting 337 | if ( c?$conn && c$conn?$conn_state ) { 338 | if ( /SF/ in c$conn$conn_state ) { 339 | return ""; 340 | } 341 | 342 | local state = c$conn$conn_state; 343 | local resp_bytes = c$resp$size; 344 | 345 | # mid stream traffic - ignore 346 | if ( state == "OTH" && resp_bytes > 0 ) { 347 | return ""; 348 | } 349 | } 350 | 351 | # if ever a SF a LBL host on this port - ignore the orig completely 352 | if ( resp in Site::host_profiles && d_port in Site::host_profiles[resp] ) 353 | return ""; 354 | 355 | # don't need to process known_scanners again 356 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) { 357 | # log_reporter(fmt("check_KnockKnockPort: orig in known_scanner"),0); 358 | return ""; 359 | } 360 | 361 | # finally a scan candidate 362 | return "PortKnock"; 363 | #add_to_knockknock_cache(orig, d_port, resp); 364 | } 365 | -------------------------------------------------------------------------------- /scripts/check-portscan.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | const activate_PortScan = T &redef; 5 | 6 | redef enum Notice::Type += { 7 | PortScan, # the source has scanned a number of ports 8 | PortScanSummary, # summary of distinct ports per scanner 9 | }; 10 | 11 | # If true, we suppress scan-checking (we still do account-tried 12 | # accounting). This is provided because scan-checking can consume 13 | # a lot of memory. 14 | const suppress_scan_checks = F &redef; 15 | 16 | global port_summary: function(t: table[addr] of set[port], orig: addr) 17 | : interval; 18 | 19 | global distinct_ports: table[addr] of set[port] &read_expire=1 days 20 | &expire_func=port_summary &redef; 21 | 22 | const port_summary_trigger = 20 &redef; 23 | 24 | global rpts_idx: table[addr, addr] of count &default=1 &read_expire=1 days &redef; 25 | 26 | # Indexed by scanner address, yields a table with scanned hosts 27 | # (and ports). 28 | global scan_triples: table[addr] of table[addr] of set[port]; 29 | 30 | # Report a scan of ports at each of these points. 31 | const report_port_scan: vector of count = { 32 | 50, 33 | 250, 34 | 1000, 35 | 5000, 36 | 10000, 37 | 25000, 38 | 65000, 39 | } &redef; 40 | 41 | # Once a source has scanned this many different ports (to however many 42 | # different remote hosts), start tracking its per-destination access. 43 | const possible_port_scan_thresh = 20 &redef; 44 | 45 | global remove_possible_source: function(s: set[addr], idx: addr): interval; 46 | global possible_scan_sources: set[addr] &expire_func=remove_possible_source 47 | &read_expire=1 days; 48 | 49 | global filterate_PortScan: function(c: connection, established: bool, 50 | reverse: bool): string; 51 | global check_PortScan: function(c: connection, established: bool, 52 | reverse: bool); 53 | 54 | global thresh_check_2: function(v: vector of count, idx: table[addr, addr] of count, 55 | orig: addr, resp: addr, n: count): bool; 56 | } 57 | 58 | # Same as thresh_check, except the index has a different type signature. 59 | function thresh_check_2(v: vector of count, idx: table[addr, addr] of count, 60 | orig: addr, resp: addr, n: count): bool 61 | { 62 | if ( ignore_scanners_threshold > 0 && n > ignore_scanners_threshold ) { 63 | ignore_addr(orig); 64 | return F; 65 | } 66 | 67 | if ( idx[orig, resp] <= |v| && n >= v[idx[orig, resp]] ) { 68 | ++idx[orig, resp]; 69 | return T; 70 | } else 71 | return F; 72 | } 73 | 74 | @if ( Cluster::is_enabled() ) 75 | export { 76 | global Scan::m_w_portscan_update_known_scanners: event(orig: addr); 77 | global Scan::w_m_portscan_new: event(orig: addr, d_port: port, resp: addr, 78 | outbound: bool); 79 | global Scan::add_to_portscan_cache: function(orig: addr, d_port: port, 80 | resp: addr); 81 | } 82 | @endif 83 | 84 | #@if ( Cluster::is_enabled() ) 85 | #@load base/frameworks/cluster 86 | #redef Cluster::manager2worker_events += /Scan::m_w_portscan_update_known_scanners/; 87 | #redef Cluster::worker2manager_events += /Scan::w_m_portscan_new/; 88 | #@endif 89 | 90 | function port_summary(t: table[addr] of set[port], orig: addr): interval 91 | { 92 | local num_distinct_ports = orig in t ? |t[orig]| : 0; 93 | 94 | if ( num_distinct_ports >= port_summary_trigger ) 95 | NOTICE([ 96 | $note=PortScanSummary, 97 | $src=orig, 98 | $n=num_distinct_ports, 99 | $msg=fmt("%s scanned a total of %d ports", orig, num_distinct_ports)]); 100 | 101 | return 0 secs; 102 | } 103 | 104 | function check_portscan_thresh(orig: addr, service: port, resp: addr): bool 105 | { 106 | if ( orig !in scan_triples ) 107 | scan_triples[orig] = table(); 108 | 109 | if ( resp !in scan_triples[orig] ) 110 | scan_triples[orig][resp] = set(); 111 | 112 | if ( service !in scan_triples[orig][resp] ) { 113 | add scan_triples[orig][resp][service]; 114 | 115 | if ( thresh_check_2(report_port_scan, rpts_idx, orig, resp, 116 | |scan_triples[orig][resp]|) ) { 117 | local m = |scan_triples[orig][resp]|; 118 | NOTICE([ 119 | $note=PortScan, 120 | $n=m, 121 | $src=orig, 122 | $p=service, 123 | $msg=fmt("%s has scanned %d ports of %s", orig, m, resp)]); 124 | return T; 125 | } 126 | } 127 | 128 | return F; 129 | } 130 | 131 | function filterate_PortScan(c: connection, established: bool, reverse: bool): string 132 | { 133 | if ( ! activate_PortScan ) 134 | return ""; 135 | 136 | return "P"; 137 | } 138 | 139 | function check_PortScan(c: connection, established: bool, reverse: bool) 140 | { 141 | #if ( suppress_scan_checks ) 142 | # return ; 143 | 144 | if ( ! established ) { 145 | # Don't consider established connections for port scanning, 146 | # it's too easy to be mislead by FTP-like applications that 147 | # legitimately gobble their way through the port space. 148 | return; 149 | 150 | local id = c$id; 151 | 152 | local service = "ftp-data" in c$service ? 20/tcp : ( reverse ? id$orig_p : 153 | id$resp_p ); 154 | local rev_service = reverse ? id$resp_p : id$orig_p; 155 | local orig = reverse ? id$resp_h : id$orig_h; 156 | local resp = reverse ? id$orig_h : id$resp_h; 157 | local outbound = Site::is_local_addr(orig); 158 | 159 | #if (orig in Scan::known_scanners) 160 | if ( Scan::known_scanners[orig]$status ) 161 | return; 162 | 163 | # Coarse search for port-scanning candidates: those that have made 164 | # connections (attempts) to possible_port_scan_thresh or more 165 | # distinct ports. 166 | if ( orig !in distinct_ports || service !in distinct_ports[orig] ) { 167 | if ( orig !in distinct_ports ) 168 | distinct_ports[orig] = set(); 169 | 170 | if ( service !in distinct_ports[orig] ) 171 | add distinct_ports[orig][service]; 172 | 173 | if ( |distinct_ports[orig]| >= possible_port_scan_thresh 174 | && orig !in scan_triples ) { 175 | scan_triples[orig] = table(); 176 | add possible_scan_sources[orig]; 177 | } 178 | } 179 | 180 | # Check for low ports. 181 | # if ( activate_LowPortTrolling && ! outbound && service < 1024/tcp && 182 | # service !in troll_skip_service ) 183 | # { 184 | # local troll_result = check_lowporttrolling(orig, service, resp); 185 | local troll_result = ""; 186 | # } 187 | 188 | # For sources that have been identified as possible scan sources, 189 | # keep track of per-host scanning. 190 | if ( orig in possible_scan_sources ) { 191 | local thresh_result = check_portscan_thresh(orig, service, resp); 192 | } 193 | 194 | @if ( Cluster::is_enabled() ) 195 | local _msg = fmt(" add_to_likely_scanner: calling w_m_portscan_new for %s, %s, %s", orig, service, resp); 196 | log_reporter(_msg, 0); 197 | 198 | event Scan::w_m_portscan_new(orig, service, resp, outbound); 199 | @endif 200 | } # end if established 201 | } 202 | 203 | @if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) 204 | event Scan::w_m_portscan_new(orig: addr, service: port, resp: addr, 205 | outbound: bool) 206 | { 207 | local msg = fmt(" inside w_m_portscan_new for %s, %s, %s", orig, service, resp); 208 | log_reporter(msg, 0); 209 | 210 | #if ( orig in Scan::known_scanners) 211 | if ( Scan::known_scanners[orig]$status ) { 212 | Scan::known_scanners[orig]$status = T; 213 | return; 214 | } 215 | 216 | if ( orig !in distinct_ports || service !in distinct_ports[orig] ) { 217 | if ( orig !in distinct_ports ) 218 | distinct_ports[orig] = set(); 219 | 220 | if ( service !in distinct_ports[orig] ) 221 | add distinct_ports[orig][service]; 222 | 223 | if ( |distinct_ports[orig]| >= possible_port_scan_thresh 224 | && orig !in scan_triples ) { 225 | scan_triples[orig] = table(); 226 | add possible_scan_sources[orig]; 227 | } 228 | } 229 | 230 | local thresh_result=F; 231 | local troll_result = F; 232 | 233 | # Check for low ports. 234 | if ( activate_LowPortTrolling 235 | && ! outbound 236 | && service < 1024/tcp 237 | && service !in troll_skip_service ) { 238 | #local troll_result = check_LowPortTroll(orig, service, resp); 239 | troll_result = F; 240 | } 241 | 242 | # For sources that have been identified as possible scan sources, 243 | # keep track of per-host scanning. 244 | if ( orig in possible_scan_sources ) { 245 | thresh_result = check_portscan_thresh(orig, service, resp); 246 | } 247 | 248 | if ( troll_result || thresh_result ) { 249 | local _msg = fmt("w_m_portscan_new: calling m_w_portscan_update_known_scanners for: %s, %s, %s", orig, service, resp); 250 | log_reporter(_msg, 0); 251 | 252 | event Scan::m_w_portscan_update_known_scanners(orig); 253 | 254 | if ( orig !in Scan::known_scanners ) { 255 | known_scanners[orig]$scanner = orig; 256 | Scan::known_scanners[orig]$status = T; 257 | } 258 | } 259 | } 260 | @endif 261 | 262 | # we can get away with only sending orig here since thats what is used to update 263 | # known_scanners table on workers , we are still sending d_port, resp 264 | # for debugging assurances 265 | 266 | @if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) 267 | event Scan::m_w_portscan_update_known_scanners(orig: addr) 268 | { 269 | if ( orig !in Scan::known_scanners ) 270 | Scan::known_scanners[orig]$status = T; 271 | 272 | local msg = fmt( 273 | "portscan: added m_w_portscan_update_known_scanners for: %s, %s, %s", orig, 274 | Scan::known_scanners[orig], |Scan::known_scanners[orig]|); 275 | log_reporter(msg, 0); 276 | } 277 | @endif 278 | # events for scan detections 279 | 280 | #event connection_established(c: connection) 281 | # { 282 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 283 | # Scan::check_PortScan(c, T, is_reverse_scan); 284 | # 285 | # #local trans = get_port_transport_proto(c$id$orig_p); 286 | # #if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 287 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 288 | # } 289 | # 290 | #event partial_connection(c: connection) 291 | # { 292 | # Scan::check_PortScan(c, T, F); 293 | # } 294 | # 295 | #event connection_attempt(c: connection) 296 | # { 297 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 298 | # Scan::check_PortScan(c, F, is_reverse_scan); 299 | # 300 | # #local trans = get_port_transport_proto(c$id$orig_p); 301 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 302 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 303 | # } 304 | # 305 | #event connection_half_finished(c: connection) 306 | # { 307 | # # Half connections never were "established", so do scan-checking here. 308 | # Scan::check_PortScan(c, F, F); 309 | # } 310 | # 311 | #event connection_rejected(c: connection) 312 | # { 313 | # local is_reverse_scan = (c$orig$state == TCP_RESET && c$id$resp_p !in likely_server_ports); 314 | # 315 | # Scan::check_PortScan(c, F, is_reverse_scan); 316 | # 317 | # #local trans = get_port_transport_proto(c$id$orig_p); 318 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 319 | # # TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 320 | # } 321 | # 322 | #event connection_reset(c: connection) 323 | # { 324 | # if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE ) 325 | # { 326 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 327 | # # We never heard from one side - that looks like a scan. 328 | # Scan::check_PortScan(c, c$orig$size + c$resp$size > 0, is_reverse_scan); 329 | # } 330 | # } 331 | # 332 | #event connection_pending(c: connection) 333 | # { 334 | # if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE ) 335 | # Scan::check_PortScan(c, F, F); 336 | # } 337 | # 338 | # 339 | -------------------------------------------------------------------------------- /scripts/check-scan-impl.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | global check_scan_cache: function(c: connection, established: bool, 5 | reverse: bool, filtrator: string); 6 | global run_scan_detection: function(ci: conn_info, established: bool, 7 | reverse: bool, filtrator: string): bool; 8 | } 9 | 10 | # Final function which calls various scan-detection heuristics, if activated 11 | # as specificed in scan-user-config.bro 12 | # 13 | # ci: conn_info - contains conn_id + first seen timestamp for the connection 14 | # established: bool - if connection was established or not 15 | # reverse: bool - if initial sy/ack was seen from the dst without a syn from orig 16 | # filtrator: string - consist of K(knockknock), L(LandMine), B(BackScatter), A(AddressScan) 17 | # if any of the above filteration is true then based on filtrator string - that specific heuristic will 18 | # be applied to the connection 19 | # 20 | # Returns: bool - returns T or F depending if IP is a scanner 21 | function Scan::run_scan_detection(ci: conn_info, established: bool, 22 | reverse: bool, filtrator: string): bool 23 | { 24 | if ( gather_statistics ) { 25 | s_counters$run_scan_detection += 1; 26 | } 27 | 28 | 29 | local cid = ci$cid; 30 | local orig = ci$cid$orig_h; 31 | 32 | local heuristic: string; 33 | 34 | if (activate_LandMine && "L" in filtrator && check_LandMine(cid, established, reverse)) { 35 | heuristic = "LandMine"; 36 | } else if (activate_SubnetKnock && "S" in filtrator && check_SubnetKnock(cid, established, reverse)) { 37 | heuristic = "SubnetKnock"; 38 | } else if (Scan::activate_KnockKnockScan && "K" in filtrator && check_KnockKnockScan(cid, established, reverse)) { 39 | heuristic = "KnockKnockScan"; 40 | } else if (Scan::activate_Backscatter && "B" in filtrator && Scan::check_Backscatter(cid, established, reverse)) { 41 | heuristic = "Backscatter"; 42 | } else if (activate_AddressScan && "A" in filtrator && check_AddressScan(cid, established, reverse)) { 43 | heuristic = "AddressScan"; 44 | } else if (activate_LowPortTrolling && "T" in filtrator && check_LowPortTroll(cid, established, reverse)) { 45 | heuristic = "LowPortTrolling"; 46 | } else { 47 | return F; 48 | } 49 | 50 | # we should not consider Backscatter as scanners 51 | # So 52 | 53 | 54 | if (heuristic != "Backscatter") 55 | { 56 | Scan::add_to_known_scanners(orig, heuristic); 57 | 58 | #adding a confirmed scanner to monitor its Subnet 59 | Scan::AddHotSubnet(orig); 60 | 61 | event Scan::PortSpike(cid$resp_p, orig); 62 | } 63 | 64 | #log_reporter (fmt("2. run_scan_detection: conn_info: %s, filterator: %s", ci, filtrator),0); 65 | return T; 66 | } 67 | 68 | function populate_table_start_ts(ci: conn_info) 69 | { 70 | local orig = ci$cid$orig_h; 71 | 72 | if ( orig !in table_start_ts ) { 73 | local st: start_ts; 74 | table_start_ts[orig] = st; 75 | table_start_ts[orig]$ts = ci$ts; 76 | } 77 | 78 | table_start_ts[orig]$conn_count += 1; 79 | 80 | # gather the smallest timestamp for that IP 81 | # different workers see different ts 82 | 83 | if ( table_start_ts[orig]$ts > ci$ts ) 84 | table_start_ts[orig]$ts = ci$ts; 85 | } 86 | 87 | # Entry point from check-scan function - this function dispatches connection to manager if cluster is enabled 88 | # or calls run_scan_detection for standalone instances 89 | # c: connection record 90 | # established: bool - if connection is established 91 | # reverse: bool - 92 | # filtrator: string - comprises of K,L,A,B depending on which one of the filteration was successful 93 | function check_scan_cache(c: connection, established: bool, reverse: bool, 94 | filtrator: string) 95 | { 96 | if ( gather_statistics ) { 97 | s_counters$check_scan_cache += 1; 98 | } 99 | 100 | local orig = c$id$orig_h; 101 | local resp = c$id$resp_h; 102 | 103 | local ci: conn_info; 104 | 105 | ci$cid = c$id; 106 | ci$ts = c$start_time; 107 | 108 | #already identified as scanner no need to proceed further 109 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) { 110 | s_counters$check_scan_counter += 1; 111 | return; 112 | } 113 | 114 | # send to proxy and/or run_scan_detection 115 | @if ( Cluster::is_enabled() ) 116 | local scan_sub=get_subnet(orig); 117 | Cluster::publish_hrw(Cluster::proxy_pool, scan_sub, Scan::potential_scanner, ci, 118 | established, reverse, filtrator); 119 | @else 120 | populate_table_start_ts(ci); 121 | #AddHotSubnet(orig); 122 | run_scan_detection(ci, established, reverse, filtrator); 123 | @endif 124 | } 125 | 126 | # Event runs on manager in cluster setup. All the workers run check_scan_cache locally and 127 | # dispatch conn_info to manager which aggregates the connections of a source IP and 128 | # calls heuristics for scan-dection 129 | # ci: conn_info - conn_id + timestamp 130 | # established: bool - if connect was established 131 | # reverse: bool 132 | # filtrator: string - comprises of K,L,A,B depending on which one of the filteration was successful 133 | # @if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::PROXY) 134 | # @endif 135 | 136 | event Scan::potential_scanner(ci: conn_info, established: bool, reverse: bool, 137 | filtrator: string) 138 | { 139 | if ( gather_statistics ) { 140 | s_counters$worker_to_manager_counter += 1; 141 | } 142 | 143 | #log_reporter(fmt("A in inside potential_scanner: %s, %s", ci, filtrator),0); 144 | 145 | local orig = ci$cid$orig_h; 146 | 147 | if (orig in Scan::known_scanners) 148 | return; 149 | 150 | #AddHotSubnet(orig); 151 | 152 | populate_table_start_ts(ci); 153 | local is_scan = Scan::run_scan_detection(ci, established, reverse, filtrator); 154 | 155 | # if successful scanner, dispatch it to all workers 156 | # this is needed to keep known_scanners table syncd on all workers 157 | if ( is_scan ) { 158 | @if ( Cluster::is_enabled() ) 159 | Broker::publish(Cluster::worker_topic, Scan::m_w_add_scanner, 160 | known_scanners[orig]); 161 | @else 162 | event Scan::m_w_add_scanner(known_scanners[orig]); 163 | @endif 164 | } 165 | } 166 | 167 | # update workers with new scanner info 168 | 169 | # @if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::WORKER) 170 | # @endif 171 | 172 | event Scan::m_w_add_scanner(ss: scan_info) 173 | { 174 | #log_reporter(fmt ("check-scan-impl: m_w_add_scanner: %s", ss), 0); 175 | local orig = ss$scanner; 176 | local detection = ss$detection; 177 | 178 | if ( orig !in known_scanners ) { 179 | Scan::add_to_known_scanners(orig, detection); 180 | 181 | # send stats (start_ts, end_ts etc to manager 182 | if ( orig in worker_stats ) { 183 | worker_stats[orig]$detection = detection; 184 | 185 | @if ( Cluster::is_enabled() ) 186 | local scan_sub = get_subnet(orig); 187 | Cluster::publish_hrw(Cluster::proxy_pool, scan_sub, Scan::aggregate_scan_stats, 188 | worker_stats[orig]); 189 | @else 190 | event Scan::aggregate_scan_stats(worker_stats[orig]); 191 | @endif 192 | } 193 | } 194 | } 195 | 196 | # populates known_scanners table and if scan_summary is enabled then 197 | # handles initialization of scan_summary table as well. 198 | # also logs first Detection entry in scan_summary 199 | # orig: addr - IP address of scanner 200 | # detect: string - what kind of scan was it - knock, address, landmine, backscatter 201 | function Scan::add_to_known_scanners(orig: addr, detect: string) 202 | { 203 | #log_reporter(fmt("3: Scanner found: [add_to_known_scanners]: orig: %s, detect: %s, %s", orig, detect, Cluster::node),0); 204 | 205 | # check if this scanner is a false positive 206 | 207 | local new = F; 208 | 209 | if ( orig !in Scan::known_scanners ) { 210 | local si: scan_info; 211 | Scan::known_scanners[orig] = si; 212 | new = T; 213 | } 214 | 215 | Scan::known_scanners[orig]$scanner = orig; 216 | Scan::known_scanners[orig]$status = T; 217 | Scan::known_scanners[orig]$detection = detect; 218 | Scan::known_scanners[orig]$detect_ts = network_time(); 219 | Scan::known_scanners[orig]$event_peer = fmt("%s", peer_description); 220 | 221 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) || ( ! Cluster::is_enabled() ) ) 222 | if ( orig in worker_stats ) 223 | worker_stats[orig]$detection = detect; 224 | @endif 225 | #log_reporter(fmt("add_to_known_scanners: known_scanners[orig]: DETECT: %s, %s, %s, %s, %s", detect, orig, Scan::known_scanners [orig], network_time(), current_time()),0); 226 | } 227 | -------------------------------------------------------------------------------- /scripts/check-scan.zeek: -------------------------------------------------------------------------------- 1 | # this is the core module which integrates and enables and disables 2 | # all the scan-detection suite 3 | 4 | 5 | module Scan; 6 | 7 | export { 8 | global check_scan: function(c: connection, established: bool, reverse: bool); 9 | #global not_scanner: function (cid: conn_id): bool ; 10 | } 11 | 12 | global uid_table: table[string] of bool &default=F &create_expire=5 mins; 13 | 14 | event zeek_init() 15 | { 16 | event table_sizes(); 17 | } 18 | 19 | # Checks if an IP qualies the criteria of being a NOT scanner 20 | # 21 | # cid: connection record 22 | # 23 | # Returns: bool - T/F depending on various conditions satisfied internally 24 | function not_scanner(cid: conn_id): bool 25 | { 26 | local orig = cid$orig_h; 27 | local orig_p = cid$orig_p; 28 | local resp = cid$resp_h; 29 | local service = cid$resp_p; 30 | local outbound = Site::is_local_addr(orig); 31 | 32 | #@ifdef (NetControl::BlockInfo) 33 | # if (is_catch_release_active(cid) ) 34 | # { 35 | # if (orig in known_scanners && cid$resp_p in Scan::skip_services) 36 | # { 37 | # local code: bool = F ; 38 | # code = NetControl::unblock_address_catch_release(cid$orig_h, "tcpsynportblock: Removing IP from catch-n-release"); 39 | # NOTICE([$note=DisableCatchRelease, $src=orig, $p=service, $id=cid, $src_peer=get_local_event_peer(), $msg=fmt ("Disable catch-n-release because %s added to skip_services", resp)]); 40 | # #log_reporter(fmt("unblock_address_catch_release: %s, %s", cid$orig_h, code), 10); 41 | # } 42 | # 43 | # return T ; 44 | # } 45 | #@endif 46 | 47 | local result = F; 48 | 49 | # blocked_nets - we don't want to put this here 50 | # since this means we ignore and not see 51 | # scanners from blocked nets anymore 52 | # moving this to post scan-detection ie populate_known_scanners 53 | #if (orig in blocked_nets) 54 | # return T ; 55 | 56 | # whitelist membership checks 57 | if ( orig in Scan::whitelist ) 58 | return T; 59 | 60 | @ifdef (YURT::CLOUDFLARE) 61 | if ( orig in YURT::CLOUDFLARE) 62 | return T; 63 | @endif 64 | 65 | if ( orig in Scan::whitelist_subnet ) 66 | return T; 67 | 68 | # ignore scan sources 69 | if ( orig in skip_scan_sources ) 70 | return T; 71 | 72 | # this is a big sword - we should not blanket 73 | # skip but rather do it per heuristic 74 | #if ( service in skip_services && ! outbound ) 75 | # return T; 76 | 77 | if ( outbound && service in skip_outbound_services ) 78 | return T; 79 | 80 | if ( orig in skip_scan_nets ) 81 | return T; 82 | 83 | # Don't include well known server/ports for scanning purposes. 84 | if ( ! outbound && [resp, service] in skip_dest_server_ports ) 85 | return T; 86 | 87 | # check for conn_history - that is if we ever saw a full SF going to this IP 88 | #f (History::check_conn_history(orig)) 89 | # return T ; 90 | 91 | # enabling udp for Landmine - aashish 2019-09-16 92 | # we only deal with tcp scanners and icmp for now 93 | # Enabling UDP on LandMine -2019-09-16 aashish 94 | #if (service >= 0/udp && service <= 65535/udp) 95 | # return T; 96 | 97 | # ignore traffic to host/port this is primarily whitelisting 98 | # maintained in ipportexclude_file for sticky config firewalled hosts 99 | if ( resp in Site::local_nets && [resp, service] in ipportexclude ) 100 | return T; 101 | 102 | # Lets ignore everything which has an outgoing established 103 | # connection from us to a given subnet 104 | # this is somewhat a big blanket of ignore 105 | 106 | #seen = bloomfilter_lookup(Scan::outgoing_SF, orig); 107 | #if (seen == 1 ) 108 | # return T; 109 | 110 | return result; 111 | } 112 | 113 | # Primary entry point of :bro:see:`Scan::check_scan` modoule 114 | # It is called by :bro:see:`new_connection`, :bro:see:`connection_state_remove`, :bro:see:`connection_established` 115 | # :bro:see:`connection_attempt`, :bro:see:`connection_rejected`, :bro:see:`partial_connection`, :bro:see:`connection_half_finished`, 116 | # :bro:see:`connection_reset`, :bro:see:`connection_pending` events 117 | # 118 | # c: connection_record :see:bro:`connection` 119 | # 120 | # established: bool - if a connection between endpoints is established 121 | # 122 | # reverse: bool - if connection is from setup from destination to source instead 123 | function check_scan(c: connection, established: bool, reverse: bool) 124 | { 125 | local orig = c$id$orig_h; 126 | 127 | 128 | 129 | # already a known_scanner 130 | if ( orig in Scan::known_scanners && Scan::known_scanners[orig]$status ) { 131 | #print fmt ("Known_scanner: %s", c$id); 132 | if ( gather_statistics ) 133 | s_counters$already_scanner_counter += 1; 134 | return; 135 | } 136 | 137 | if ( not_scanner(c$id) ) { 138 | if ( gather_statistics ) 139 | s_counters$not_scanner += 1; 140 | return; 141 | } 142 | 143 | #log_reporter(fmt ("check_scan: scanner: orig in known_scanners for %s", c$id$orig_h),10); 144 | 145 | local resp = c$id$resp_h; 146 | 147 | # if darknet then fast-pace the detection for landmine, knockknoc and 148 | # backscatter so that we don't ahve to wait till tcp_attempt_delay expiration 149 | # of 5 sec 150 | 151 | local darknet = F; 152 | 153 | if ( Site::is_local_addr(resp) && resp !in Scan::subnet_table ) { 154 | darknet = T; 155 | 156 | 157 | if (orig in hs || isHotSubnet(orig)) 158 | darknet = T ; 159 | 160 | #print fmt ("DARKNET: %s, %s", c$uid, c$id); 161 | 162 | if ( gather_statistics ) 163 | s_counters$darknet_counter += 1; 164 | } # only watch for live subnets - since conn_state_remove adds 165 | # 5.0 sec of latency to tcp_attempt_delay 166 | # Unsuccessful is defined as at least tcp_attempt_delay seconds 167 | # having elapsed since the originator first sent a connection 168 | # establishment packet to the destination without seeing a reply. 169 | else if ( Site::is_local_addr(resp) && resp in Scan::subnet_table ) { 170 | if ( gather_statistics ) 171 | s_counters$not_darknet_counter += 1; 172 | darknet = F; 173 | } 174 | 175 | local filter__Backscatter = ""; 176 | local filter__SubnetKnock = "" ; 177 | local filter__KnockKnock = ""; 178 | local filter__LandMine = ""; 179 | local filter__AddressScan = ""; 180 | local filter__PortScan = ""; 181 | local filter_port_knock = ""; 182 | local filter__LowPortTroll = ""; 183 | 184 | # run filteration code on the workers for each scan module 185 | # if a connectiond doesn't fit what is eventually one of the criterias of a 186 | # detection heuristic, filteration for that heuristic is a F 187 | # only connections with T filteration are processed further to be analyzed 188 | 189 | # (ii) we check against uid_table[c$uid] because if conn is already sent to manager 190 | # based on earlier event (eg. conn_attempt) we save extra processing for subsiquent 191 | # events (eg. conn_state_remove) 192 | 193 | # only check landmine if darknet ip 194 | if ( activate_LandMine && ! uid_table[c$uid] && darknet ) 195 | filter__LandMine = Scan::filterate_LandMineScan(c, darknet); 196 | if ( activate_Backscatter && ! uid_table[c$uid] ) 197 | filter__Backscatter = Scan::filterate_Backscatter(c, darknet); 198 | 199 | if ( activate_KnockKnockScan && ! uid_table[c$uid] ) 200 | filter__KnockKnock = Scan::filterate_KnockKnockScan(c, darknet); 201 | 202 | if ( activate_SubnetKnock && ! uid_table[c$uid] ) 203 | filter__SubnetKnock = Scan::filterate_SubnetKnock(c, darknet); 204 | 205 | if ( activate_AddressScan && ! uid_table[c$uid] ) 206 | filter__AddressScan = Scan::filterate_AddressScan(c, established, reverse); 207 | 208 | if ( activate_LowPortTrolling && ! uid_table[c$uid] ) 209 | filter__LowPortTroll = Scan::filterate_LowPortTroll(c, established, reverse); 210 | 211 | if ( activate_PortScan && ! uid_table[c$uid] ) 212 | filter__PortScan = Scan::filterate_PortScan(c, established, reverse); 213 | 214 | 215 | if ( /K/ in filter__KnockKnock 216 | || /S/ in filter__SubnetKnock 217 | || /L/ in filter__LandMine 218 | || /B/ in filter__Backscatter 219 | || /A/ in filter__AddressScan 220 | || /T/ in filter__LowPortTroll 221 | || /P/ in filter__PortScan ) { 222 | # So connection met one or more of heuristic filteration criterias 223 | # send for further determination into check-scan-impl.bro now 224 | 225 | if ( gather_statistics ) 226 | s_counters$filteration_success += 1; 227 | 228 | # we maintain a uid_table with create_expire of 30 secs so that same connection processed by one event 229 | # is not again sent - for example if C is already processed in scan-engine for new_connection, lets not 230 | # process same C for subsiquent TCP events such as conn_terminate or conn_rejected etc. 231 | if ( c$uid !in uid_table ) { 232 | local filterator = fmt("%s%s%s%s%s%s%s", filter__KnockKnock, filter__LandMine, 233 | filter__Backscatter, filter__AddressScan, 234 | filter__PortScan, filter__LowPortTroll,filter__SubnetKnock); 235 | uid_table[c$uid] = T; 236 | check_scan_cache(c, established, reverse, filterator); 237 | add scan_candidates[c$id$orig_h]; 238 | } 239 | } 240 | } 241 | 242 | # speed up landmine and knockknock for darknet space 243 | event new_connection(c: connection) 244 | { 245 | #print fmt ("new_connection"); 246 | # for new connections we just want to supply C and only for darknet spaces 247 | # to speed up reaction time and to avoind tcp_expire_delays of 5.0 sec 248 | 249 | # only external IPs 250 | if ( c$id$orig_h in Site::local_nets ) 251 | return; 252 | 253 | # don't process known_scanners 254 | if ( c$id$orig_h in Scan::known_scanners ) 255 | return; 256 | 257 | #print fmt ("c$id$orig_h: %s, known_scanners: %s, counters: %s ", c$id$orig_h, known_scanners, s_counters); 258 | 259 | if ( gather_statistics ) { 260 | s_counters$event_peer = fmt("%s", peer_description); 261 | s_counters$new_conn_counter += 1; 262 | } 263 | 264 | local tp = get_port_transport_proto(c$id$resp_p); 265 | 266 | if ( ( tp == tcp || tp == udp ) && c$id$orig_h !in Site::local_nets && 267 | (is_darknet(c$id$resp_h) || isHotSubnet(c$id$orig_h)) ) { 268 | #print fmt ("-1: event new_connection (c: connection): %s", c$id); 269 | Scan::check_scan(c, F, F); 270 | } 271 | } 272 | 273 | event connection_state_remove(c: connection) 274 | { 275 | # only external IPs 276 | if ( c$id$orig_h in Site::local_nets ) 277 | return; 278 | 279 | # don't process known_scanners 280 | if ( c$id$orig_h in Scan::known_scanners ) 281 | return; 282 | 283 | local id = c$id; 284 | local service = id$resp_p; 285 | 286 | local trans = get_port_transport_proto(service); 287 | 288 | # don't operate on a connection which responder 289 | # sends data back in a udp connection ie c$history = d 290 | 291 | if ( (( trans == udp ) || (trans == tcp) || (trans == icmp )) 292 | && (c$conn$conn_state != "SF") 293 | && ( /d|D/ !in c$history ) ) { 294 | 295 | check_scan(c, T, F); 296 | } 297 | } 298 | 299 | event connection_established(c: connection) 300 | { 301 | # only external IPs 302 | if ( c$id$orig_h in Site::local_nets ) 303 | return; 304 | 305 | # don't process known_scanners 306 | if ( c$id$orig_h in Scan::known_scanners ) 307 | return; 308 | 309 | #print fmt("connection_established"); 310 | 311 | local is_reverse_scan = ( c$orig$state == TCP_INACTIVE && c$id$resp_p !in 312 | likely_server_ports ); 313 | #print fmt ("0. event connection_established (c: connection): %s", c$id); 314 | Scan::check_scan(c, T, is_reverse_scan); 315 | 316 | local trans = get_port_transport_proto(c$id$orig_p); 317 | if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 318 | TRW::check_TRW_scan(c, conn_state(c, trans), F); 319 | } 320 | 321 | event partial_connection(c: connection) 322 | { 323 | # only external IPs 324 | if ( c$id$orig_h in Site::local_nets ) 325 | return; 326 | 327 | # don't process known_scanners 328 | if ( c$id$orig_h in Scan::known_scanners ) 329 | return; 330 | 331 | Scan::check_scan(c, F, F); 332 | } 333 | 334 | event connection_attempt(c: connection) 335 | { 336 | #print fmt("connection_attempt"); 337 | 338 | # only external IPs 339 | if ( c$id$orig_h in Site::local_nets ) 340 | return; 341 | 342 | # don't process known_scanners 343 | if ( c$id$orig_h in Scan::known_scanners ) 344 | return; 345 | 346 | local is_reverse_scan = ( c$orig$state == TCP_INACTIVE && c$id$resp_p !in 347 | likely_server_ports ); 348 | Scan::check_scan(c, F, is_reverse_scan); 349 | 350 | local trans = get_port_transport_proto(c$id$orig_p); 351 | if ( trans == tcp && TRW::use_TRW_algorithm ) 352 | TRW::check_TRW_scan(c, conn_state(c, trans), F); 353 | } 354 | 355 | event connection_half_finished(c: connection) 356 | { 357 | #print fmt ("conn_half_finished"); 358 | # only external IPs 359 | if ( c$id$orig_h in Site::local_nets ) 360 | return; 361 | 362 | # don't process known_scanners 363 | if ( c$id$orig_h in Scan::known_scanners ) 364 | return; 365 | 366 | # Half connections never were "established", so do scan-checking here. 367 | Scan::check_scan(c, F, F); 368 | } 369 | 370 | event connection_rejected(c: connection) 371 | { 372 | if ( c$id$orig_h in Site::local_nets ) 373 | return; 374 | 375 | # don't process known_scanners 376 | if ( c$id$orig_h in Scan::known_scanners ) 377 | return; 378 | 379 | local is_reverse_scan = ( c$orig$state == TCP_RESET && c$id$resp_p !in 380 | likely_server_ports ); 381 | Scan::check_scan(c, F, is_reverse_scan); 382 | local trans = get_port_transport_proto(c$id$orig_p); 383 | 384 | if ( trans == tcp && TRW::use_TRW_algorithm ) 385 | TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 386 | } 387 | 388 | event connection_reset(c: connection) 389 | { 390 | # only external IPs 391 | if ( c$id$orig_h in Site::local_nets ) 392 | return; 393 | 394 | # don't process known_scanners 395 | if ( c$id$orig_h in Scan::known_scanners ) 396 | return; 397 | 398 | if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE ) { 399 | local is_reverse_scan = ( c$orig$state == TCP_INACTIVE && c$id$resp_p !in 400 | likely_server_ports ); 401 | # We never heard from one side - that looks like a scan. 402 | Scan::check_scan(c, c$orig$size + c$resp$size > 0, is_reverse_scan); 403 | } 404 | } 405 | 406 | event connection_pending(c: connection) 407 | { 408 | # only external IPs 409 | if ( c$id$orig_h in Site::local_nets ) 410 | return; 411 | 412 | # don't process known_scanners 413 | if ( c$id$orig_h in Scan::known_scanners ) 414 | return; 415 | 416 | if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE ) 417 | Scan::check_scan(c, F, F); 418 | } 419 | # no need of these TRW Events 420 | #event connection_established(c: connection) 421 | #{ 422 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE); 423 | # local trans = get_port_transport_proto(c$id$orig_p); 424 | # 425 | # if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 426 | # TRW::check_TRW_scan(c, conn_state(c, trans), F); 427 | #} 428 | # 429 | #event connection_attempt(c: connection) 430 | #{ 431 | # local trans = get_port_transport_proto(c$id$orig_p); 432 | # if ( trans == tcp && TRW::use_TRW_algorithm ) 433 | # TRW::check_TRW_scan(c, conn_state(c, trans), F); 434 | #} 435 | # 436 | #event connection_rejected(c: connection) 437 | #{ 438 | # local is_reverse_scan = c$orig$state == TCP_RESET; 439 | # 440 | # local trans = get_port_transport_proto(c$id$orig_p); 441 | # if ( trans == tcp && TRW::use_TRW_algorithm ) 442 | # TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 443 | #} 444 | # 445 | -------------------------------------------------------------------------------- /scripts/conn-history.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | global outgoing_SF: set[addr] &read_expire=6 hrs ; #&backend=Broker::MEMORY; 5 | global conn_duration: set[addr] &read_expire=6hrs ; #&backend=Broker::MEMORY; 6 | 7 | redef enum Notice::Type += { 8 | OutgoingSF, # If TCP_ESTABLISHED to remote Scanner 9 | LongDuration, 10 | }; 11 | 12 | global check_conn_history: function(ip: addr): bool; 13 | } 14 | 15 | event Scan::m_w_add_scanner(ss: Scan::scan_info) 16 | { 17 | local ip = ss$scanner; 18 | 19 | # not sure why we are doing subnet 20 | # this is not forgiving and FP prone 21 | #local ds = get_subnet(ip); 22 | 23 | if ( ip in outgoing_SF ) { 24 | NOTICE([ 25 | $note=OutgoingSF, 26 | $src=ip, 27 | $msg=fmt("outgoing SF to IP flagged as scanner %s", ip)]); 28 | } 29 | 30 | if ( ip in conn_duration ) { 31 | NOTICE([ 32 | $note=LongDuration, 33 | $src=ip, 34 | $msg=fmt("known long duration connections from this scanner IP: %s", ip)]); 35 | } 36 | } 37 | 38 | # good guys == IP which successfully accepted a connection 39 | # originating from the local_nets 40 | 41 | event connection_established(c: connection) &priority=-5 42 | { 43 | local src = c$id$orig_h; 44 | local dst = c$id$resp_h; 45 | 46 | if ( src !in Site::local_nets ) 47 | return; 48 | 49 | if ( c$resp$state != TCP_ESTABLISHED ) 50 | return; 51 | 52 | local trans = get_port_transport_proto(c$id$orig_p); 53 | if (trans != tcp) 54 | return; 55 | 56 | if (/\^/ in c$history) 57 | return; 58 | 59 | add outgoing_SF[dst]; 60 | } 61 | 62 | event connection_state_remove(c: connection) &priority=-5 63 | { 64 | #if ( c$conn$proto == udp || c$conn$proto == icmp ) 65 | # return; 66 | 67 | local src = c$id$orig_h; 68 | 69 | if ( src !in Site::local_nets ) 70 | return; 71 | 72 | if ( c$duration > 60 secs ) { 73 | add conn_duration[src]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /scripts/debug.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | const DEBUG = 1; 5 | 6 | global log_reporter: function(msg: string, debug: count); 7 | } 8 | 9 | function log_reporter(msg: string, debug: count) 10 | { 11 | if ( DEBUG >= 0 ) { 12 | @if ( ! Cluster::is_enabled() ) 13 | print fmt("%s", msg); 14 | @endif 15 | event reporter_info(network_time(), msg, peer_description); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/expire-known-scanners.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | 5 | redef enum Notice::Type += { 6 | Removed, 7 | NotRemoved, 8 | }; 9 | 10 | } 11 | 12 | event Scan::known_scanner_remove( idx: addr, bi: NetControl::BlockInfo) 13 | { 14 | local _msg = ""; 15 | 16 | if ( idx in Scan::known_scanners) 17 | { 18 | delete Scan::known_scanners[idx] ; 19 | _msg = fmt ("%s is removed from known_scanners on [%s] %s. Total known_scanners: %s", idx, bi, peer_description, |Scan::known_scanners|); 20 | 21 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::PROXY ) || ( ! Cluster::is_enabled() ) ) 22 | NOTICE([$note=Scan::Removed, $src=idx, $msg=fmt("%s", _msg)]); 23 | @endif 24 | } 25 | # not needed 26 | # else 27 | # { 28 | # _msg = fmt ("%s is not in scan::known_scanners: %s", idx, peer_description) ; 29 | # NOTICE([$note=Scan::NotRemoved, $src=idx, $msg=fmt("%s", _msg)]); 30 | # } 31 | 32 | } 33 | 34 | 35 | event NetControl::catch_release_forgotten(idx: addr, bi: NetControl::BlockInfo) 36 | { 37 | Broker::publish(Cluster::worker_topic, Scan::known_scanner_remove, idx, bi); 38 | Broker::publish(Cluster::proxy_topic, Scan::known_scanner_remove, idx, bi); 39 | } 40 | -------------------------------------------------------------------------------- /scripts/host-profiling.zeek: -------------------------------------------------------------------------------- 1 | module Site; 2 | 3 | export { 4 | # table to maintain list of all active hosts in local_nets with open ports 5 | global host_profiles: table[addr] of set[port] &read_expire=2 days; 6 | 7 | global Site::w_m_new_host_profile: event(cid: conn_id); 8 | global Site::m_w_add_host_profiles: event(cid: conn_id); 9 | 10 | redef enum Log::ID += { 11 | host_open_ports_LOG 12 | }; 13 | 14 | type Info: record { 15 | # When the email was seen. 16 | ts: time &log; 17 | #id: conn_id &log ; 18 | host: addr &log &optional; 19 | d_port: port &log &optional; 20 | peer: string &log &optional; 21 | #services: string &log &optional; 22 | }; 23 | 24 | global log_host_profiles: function(cid: conn_id); 25 | } 26 | 27 | event zeek_init() &priority=5 28 | { 29 | Log::create_stream(Site::host_open_ports_LOG, [$columns=Info]); 30 | } 31 | 32 | function log_host_profiles(cid: conn_id) 33 | { 34 | local info: Info; 35 | 36 | info$ts = network_time(); 37 | #info$id = cid ; 38 | info$host = cid$resp_h; 39 | info$d_port = cid$resp_p; 40 | #info$services = "" ; 41 | 42 | info$peer = peer_description; 43 | Log::write(Site::host_open_ports_LOG, info); 44 | } 45 | 46 | #@if ( Cluster::is_enabled() ) 47 | #@load base/frameworks/cluster 48 | #redef Cluster::manager2worker_events += /Site::m_w_add_host_profiles/; 49 | #redef Cluster::worker2manager_events += /Site::w_m_new_host_profile/; 50 | #@endif 51 | 52 | @if ( Cluster::is_enabled() ) 53 | 54 | @if ( Cluster::local_node_type() == Cluster::MANAGER ) 55 | event zeek_init() 56 | { 57 | Broker::auto_publish(Cluster::worker_topic, Site::m_w_add_host_profiles); 58 | } 59 | @else 60 | event zeek_init() 61 | { 62 | Broker::auto_publish(Cluster::manager_topic, Site::w_m_new_host_profile); 63 | } 64 | @endif 65 | 66 | @endif 67 | 68 | function add_to_host_profile_cache(cid: conn_id) 69 | { 70 | local orig = cid$orig_h; 71 | local resp = cid$resp_h; 72 | local d_port = cid$resp_p; 73 | 74 | if ( orig in Site::local_nets ) 75 | return; 76 | 77 | if ( resp !in host_profiles ) 78 | host_profiles[resp] = set(); 79 | 80 | if ( d_port !in host_profiles[resp] ) { 81 | #Scan::log_reporter(fmt ("add_to_host_profile_cache %s", cid)); 82 | add host_profiles[resp][d_port]; 83 | 84 | local _services = ""; 85 | for ( s in host_profiles[resp] ) 86 | _services += fmt(" %s ", s); 87 | #print fmt ("%s has services on %s", resp, _services) ; 88 | 89 | @if ( Cluster::is_enabled() ) 90 | event Site::w_m_new_host_profile(cid); 91 | @endif 92 | } 93 | } 94 | 95 | @if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) 96 | event Site::w_m_new_host_profile(cid: conn_id) 97 | { 98 | local orig = cid$orig_h; 99 | local resp = cid$resp_h; 100 | local d_port = cid$resp_p; 101 | 102 | #Scan::log_reporter(fmt ("w_m_new_host_profile: %s", cid)); 103 | 104 | if ( resp !in host_profiles ) 105 | host_profiles[resp] = set(); 106 | 107 | if ( d_port !in host_profiles[resp] ) { 108 | add host_profiles[resp][d_port]; 109 | log_host_profiles(cid); 110 | } 111 | event Site::m_w_add_host_profiles(cid); 112 | } 113 | @endif 114 | 115 | @if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) 116 | event Site::m_w_add_host_profiles(cid: conn_id) 117 | { 118 | local orig = cid$orig_h; 119 | local resp = cid$resp_h; 120 | local d_port = cid$resp_p; 121 | 122 | #Scan::log_reporter(fmt ("m_w_add_host_profiles: %s", cid)); 123 | 124 | if ( resp !in host_profiles ) 125 | host_profiles[resp] = set(); 126 | 127 | if ( d_port !in host_profiles[resp] ) { 128 | add host_profiles[resp][d_port]; 129 | } 130 | } 131 | @endif 132 | 133 | event connection_established(c: connection) 134 | { 135 | local orig = c$id$orig_h; 136 | local resp = c$id$resp_h; 137 | local d_port = c$id$resp_p; 138 | 139 | # if outgoing traffic, exit 140 | if ( Site::is_local_addr(resp) ) { 141 | add_to_host_profile_cache(c$id); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /scripts/hotsubnets.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | #redef exit_only_after_terminate = T ; 4 | 5 | export { 6 | redef enum Notice::Type += { 7 | HotSubnet, # Too many scanners originating from this subnet 8 | }; 9 | 10 | global hot_subnets: table[subnet] of set[addr] &create_expire=7days; 11 | global hs: set[subnet] &backend=Broker::MEMORY &create_expire=7days; 12 | global hot_subnets_idx: table[subnet] of count &create_expire=7days; 13 | 14 | global hot_subnets_threshold: vector of count = { 10, 25, 100, 200, 255, 500, 15 | 1000, 5000, 10000, 20000, 30000, 50000, 100000, }; 16 | 17 | global Scan::AddHotSubnet: function(ip: addr); 18 | global Scan::evHotSubnet: event(ip: addr); 19 | global Scan::isHotSubnet: function(ip: addr): bool; 20 | 21 | global check_subnet_threshold: function(v: vector of count, idx: table[subnet] of 22 | count, orig: subnet, n: count): bool; 23 | } 24 | 25 | function check_subnet_threshold(v: vector of count, idx: table[subnet] of count, 26 | orig: subnet, n: count): bool 27 | { 28 | if ( orig !in idx ) 29 | idx[orig] = 0; 30 | 31 | # print fmt ("orig: %s and IDX_orig: %s and n is: %s and v[idx[orig]] is: %s", orig, idx[orig], n, v[idx[orig]]); 32 | 33 | if ( idx[orig] < |v| && n >= v[idx[orig]] ) 34 | { 35 | ++idx[orig]; 36 | 37 | return ( T ); 38 | } 39 | else 40 | return ( F ); 41 | } 42 | 43 | function Scan::AddHotSubnet(orig: addr) 44 | { 45 | if ( orig in Site::local_nets ) 46 | return; 47 | 48 | if ( orig in Scan::ignore_hot_subnets ) 49 | return; 50 | 51 | @ifdef ( YURT::NEVER_DROP_NETS ) 52 | if ( orig in YURT::NEVER_DROP_NETS ) 53 | return; 54 | @endif 55 | 56 | event Scan::evHotSubnet(orig); 57 | } 58 | 59 | #event connection_state_remove(c: connection) 60 | #{ 61 | # # only external IPs 62 | # if ( c$id$orig_h in Site::local_nets ) 63 | # return; 64 | # 65 | # if (c$id$orig_h in Scan::ignore_hot_subnets) 66 | # return; 67 | # 68 | #@ifdef (YURT::NEVER_DROP_NETS) 69 | # if (c$id$orig_h in YURT::NEVER_DROP_NETS) 70 | # return; 71 | #@endif 72 | # 73 | # local id = c$id; 74 | # local service = id$resp_p; 75 | # 76 | # local trans = get_port_transport_proto(service); 77 | # 78 | # # don't operate on a connection which responder 79 | # # sends data back in a tcp connection ie c$history = d 80 | # 81 | # if ( (trans == tcp) 82 | # && (c$conn$conn_state == "S0") 83 | # && ( /d|D/ !in c$history ) ) { 84 | # 85 | # # 2022-07-26 - if a scan-candidate 86 | # # enable subnet scan heuristics 87 | # # as long as no data is transferred 88 | # if (c$id$orig_h in Scan::scan_candidates && c$id$resp_p !in ignore_hot_subnets_ports) 89 | # { 90 | # local ss = get_subnet(c$id$orig_h); 91 | # #AddHotSubnet(c$id$orig_h); 92 | # } 93 | # } 94 | #} 95 | 96 | event Scan::evHotSubnet(ip: addr) 97 | { 98 | # check for subnet scanners 99 | 100 | local scanner_subnet = get_subnet(ip); 101 | 102 | #if ( scanner_subnet !in hot_subnets ) { 103 | if ( ! check_subnet(scanner_subnet, hot_subnets) ) 104 | { 105 | local a: set[addr]; 106 | hot_subnets[scanner_subnet] = a; 107 | } 108 | 109 | add hot_subnets[scanner_subnet][ip]; 110 | add hs[scanner_subnet]; 111 | 112 | local n = |hot_subnets[scanner_subnet]|; 113 | 114 | local result = F; 115 | result = check_subnet_threshold(hot_subnets_threshold, hot_subnets_idx, 116 | scanner_subnet, n); 117 | 118 | if ( result ) 119 | { 120 | local _msg = fmt("%s has %s scanners originating from it", scanner_subnet, n); 121 | NOTICE([ $note=HotSubnet, $src=ip, $msg=fmt("%s", _msg) ]); 122 | } 123 | 124 | # if ( ip in blocked_nets ) { 125 | # local msg = fmt("%s is Scanner from blocknet %s", ip, blocked_nets[ip]); 126 | # NOTICE([$note=BlocknetsIP, $src=ip, $msg=fmt("%s", msg)]); 127 | # } 128 | } 129 | 130 | function Scan::isHotSubnet(ip: addr): bool 131 | { 132 | local ss = get_subnet(ip); 133 | 134 | #check_subnet is a bif to check membership 135 | # of a subnet in a table 136 | # more accurate than `in` 137 | if ( ! check_subnet(ss, hot_subnets) ) 138 | return F; 139 | 140 | if ( hot_subnets_idx[ss] >= 1 ) 141 | return T; 142 | else 143 | return F; 144 | } 145 | -------------------------------------------------------------------------------- /scripts/identify-web-spiders.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | redef enum Notice::Type += { 5 | WebCrawler, 6 | }; 7 | 8 | global ok_web_bots: pattern = /bot|spider\.html|baidu|letsencrypt/; 9 | global ok_robots: pattern = /robots\.txt/; 10 | } 11 | 12 | event http_request(c: connection, method: string, original_URI: string, 13 | unescaped_URI: string, version: string) &priority=3 14 | { 15 | if ( ok_robots in original_URI ) { 16 | local orig = c$id$orig_h; 17 | #print fmt ("IP: %s, method: %s, original_URI: %s, unescaped_URI: %s, version: %s", orig, method, original_URI, unescaped_URI, version); 18 | if ( orig !in Scan::whitelist ) { 19 | local _msg = fmt("web-spider seeking %s", original_URI); 20 | NOTICE([$note=WebCrawler, $src=orig, $msg=fmt("%s", _msg)]); 21 | 22 | add whitelist[orig]; 23 | } 24 | } 25 | } 26 | 27 | event http_header(c: connection, is_orig: bool, name: string, value: string) 28 | &priority=2 29 | { 30 | if ( name == "USER-AGENT" && ok_web_bots in value ) { 31 | #print fmt ("name: %s, value: %s", name, value); 32 | local orig = c$id$orig_h; 33 | 34 | if ( orig !in Scan::whitelist ) { 35 | local _msg = fmt("%s crawler is seen: %s", orig, value); 36 | NOTICE([$note=WebCrawler, $src=orig, $msg=fmt("%s", _msg)]); 37 | add whitelist[orig]; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/interest-conflict.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | 4 | export { 5 | 6 | redef enum Notice::Type += { 7 | Conflict, 8 | }; 9 | 10 | } 11 | 12 | event Scan::evHotInterest(cid: conn_id) 13 | { 14 | # check for subnet scanners 15 | 16 | local resp=cid$resp_h; 17 | local orig=cid$orig_h; 18 | local svc=cid$resp_p; 19 | 20 | local ss = get_subnet(orig); 21 | local _msg = ""; 22 | 23 | if ( ss in Scan::hot_subnets && |Scan::hot_subnets[ss]| > 5 && interests_idx[ss] > 4 ) 24 | { 25 | _msg = fmt ("%s flagged as Interests where as it is also Scan::HotSubnets: [ %s ]", ss, |Scan::hot_subnets[ss]|); 26 | NOTICE([$note=Conflict, $id=cid, $msg=fmt("%s", _msg)]); 27 | } 28 | 29 | #if (orig in Scan::hot_subnets && |Scan::hot_subnets[ss] > 5 && interests_idx[ss] > 4) 30 | #{ 31 | # _msg = fmt ("%s [%s] flagged as HotSubnets %s where as it is also Interests: %s", orig, ss, Scan::interests_idx[ss], Scan::hot_subnets[ss]); 32 | # NOTICE([$note=Conflict, $id=cid, $msg=fmt("%s", _msg)]); 33 | #} 34 | } 35 | 36 | 37 | event Scan::evHotSubnet(ip: addr) 38 | { 39 | 40 | local ss = get_subnet(ip); 41 | 42 | if (ip in Scan::interests_idx && interests_idx[ss] > 4) 43 | { 44 | local _msg = fmt ("%s flagged as HotSubnet where as it is also Scan::Interests: %s", ip, Scan::interests_idx[ip]); 45 | NOTICE([$note=Conflict, $src=ip, $msg=fmt("%s", _msg)]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scripts/interests.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | redef enum Notice::Type += { 5 | Interest, 6 | }; 7 | 8 | type probes: record { 9 | remote_sub: subnet; 10 | remotes: set[addr]; 11 | svc: port; 12 | }; 13 | 14 | global Scan::evHotInterest: event(cid: conn_id); 15 | 16 | global interests: table[addr, port] of table [subnet] of probes &create_expire=7 days; 17 | global interests_idx: table[subnet] of count &create_expire=7 days; 18 | 19 | global interests_threshold: vector of count = { 20 | 4, 21 | 10, 22 | 25, 23 | 100, 24 | 200, 25 | 255, 26 | 500, 27 | 1000, 28 | 5000, 29 | 10000, 30 | 20000, 31 | 30000, 32 | 50000, 33 | 100000, 34 | }; 35 | } 36 | 37 | function Scan::AddInterests(cid: conn_id) 38 | { 39 | local orig = cid$orig_h; 40 | local service = cid$resp_p; 41 | local resp=cid$resp_h; 42 | 43 | @if ( Cluster::is_enabled() ) 44 | local scan_sub = get_subnet(orig); 45 | #local u = fmt ("%s%s",resp,service); 46 | Cluster::publish_hrw(Cluster::proxy_pool, scan_sub, Scan::evHotInterest, cid); 47 | @else 48 | event Scan::evHotInterest(cid); 49 | @endif 50 | 51 | } 52 | 53 | 54 | event connection_state_remove(c: connection) 55 | { 56 | local cid=c$id; 57 | local service = cid$resp_p; 58 | 59 | # only external IPs 60 | if ( c$id$orig_h in Site::local_nets ) 61 | return; 62 | 63 | local ss = get_subnet(c$id$orig_h); 64 | 65 | if (check_subnet(ss,hot_subnets)) 66 | return; 67 | 68 | local id = c$id; 69 | 70 | 71 | local trans = get_port_transport_proto(service); 72 | 73 | # don't operate on a connection which responder 74 | # sends data back in a tcp connection ie c$history = d 75 | 76 | if ( (trans == tcp) 77 | && ( /d|D|ShFgfG/ in c$history || "SF" in c$conn$conn_state) ) { 78 | AddInterests(c$id); 79 | } 80 | } 81 | 82 | event Scan::evHotInterest(cid: conn_id) 83 | { 84 | # check for subnet scanners 85 | 86 | local resp=cid$resp_h; 87 | local orig=cid$orig_h; 88 | local svc=cid$resp_p; 89 | 90 | local ss = get_subnet(orig); 91 | 92 | #if ( scanner_subnet !in hot_subnets ) { 93 | if ([resp,svc] !in interests) 94 | interests[resp,svc] = table(); 95 | 96 | if (ss !in interests[resp,svc]) 97 | { 98 | local p: probes; 99 | interests[resp,svc][ss] = p; 100 | } 101 | 102 | 103 | interests[resp,svc][ss]$remote_sub = ss; 104 | add interests[resp,svc][ss]$remotes [orig]; 105 | interests[resp,svc][ss]$svc = cid$resp_p; 106 | 107 | local n = |interests[resp,svc][ss]$remotes|; 108 | #print fmt ("n is %s", n); 109 | 110 | local result = F; 111 | result = check_subnet_threshold(interests_threshold, interests_idx, ss, n); 112 | 113 | if (result) 114 | { 115 | local _msg = fmt("%s has %s hosts probing %s on %s", ss, n, resp, cid$resp_p); 116 | NOTICE([$note=Interest, $src=resp, $msg=fmt("%s", _msg)]); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /scripts/netcontrol-scan-rules.zeek: -------------------------------------------------------------------------------- 1 | module NetControl; 2 | 3 | @if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) 4 | 5 | event NetControl::rule_expire(r: Rule, p: PluginState) &priority=-5 6 | { 7 | local ip = subnet_to_addr(r$entity$ip); 8 | #Scan::log_reporter(fmt ("acld_rule_expire: Rule: %s", subnet_to_addr(r$entity$ip)),1); 9 | } 10 | 11 | event NetControl::catch_release_forgotten(a: addr, bi: BlockInfo) 12 | { 13 | #Scan::log_reporter(fmt("netcontrol: catoch_release_forgotten: %s: %s", a, bi),0); 14 | 15 | # re-enabling scan-detection once netcontrol block is removed 16 | if ( a in Scan::known_scanners ) { 17 | Scan::known_scanners[a]$status = F; 18 | # send the status to all workers ; 19 | event Scan::m_w_update_scanner(a, F); 20 | Scan::log_reporter(fmt( 21 | "netcontro: catch_release_forgotten: m_w_update_scanner: F %s", a), 1); 22 | } else 23 | Scan::log_reporter(fmt("netcontro: IP !in known_scanners: %s", a), 1); 24 | } 25 | 26 | event NetControl::rule_added(r: Rule, p: PluginState, msg: string &default="") 27 | &priority=5 28 | { 29 | local ip = subnet_to_addr(r$entity$ip); 30 | #event Scan::m_w_send_known_scan_stats(ip, T); 31 | #Scan::log_reporter(fmt ("netcontro: m_w_send_known_scan_stats: %s", subnet_to_addr(r$entity$ip)),1); 32 | Scan::log_reporter(fmt("acld_rule_added: Rule: %s, %s", subnet_to_addr( 33 | r$entity$ip), r), 1); 34 | 35 | if ( /Re-drop/ in r$location ) { 36 | Scan::log_reporter(fmt("netcontrol: event rule_added: %s, %s", ip, r), 1); 37 | Scan::known_scanners[ip]$status = T; 38 | # send the status to all workers ; 39 | event Scan::m_w_update_scanner(ip, T); 40 | } 41 | } 42 | 43 | event NetControl::rule_removed(r: Rule, p: PluginState, msg: string &default="") 44 | &priority=-5 45 | { 46 | local ip = subnet_to_addr(r$entity$ip); 47 | 48 | Scan::log_reporter(fmt("acld_rule_removed: Rule: %s, %s", subnet_to_addr( 49 | r$entity$ip), r), 1); 50 | 51 | # no need to send this - we piggyback on m_w_update_scanner 52 | # event Scan::m_w_send_scan_summary_stats(ip, T); 53 | # Scan::log_reporter(fmt ("netcontro: acld_remove: m_w_send_known_scan_stats: %s", subnet_to_addr(r$entity$ip)),1); 54 | 55 | # re-enabling scan-detection once netcontrol block is removed 56 | # if (ip in Scan::known_scanners) 57 | # { 58 | # Scan::known_scanners[ip]$status = F ; 59 | # # send the status to all workers ; 60 | # event Scan::m_w_update_scanner(ip, F ); 61 | # Scan::log_reporter(fmt ("netcontro: event m_w_update_scanner: F %s", ip),1); 62 | # } 63 | # else 64 | # Scan::log_reporter(fmt ("netcontro: IP !in known_scanners: %s", ip),1) ; 65 | } 66 | 67 | event NetControl::rule_timeout(r: Rule, i: FlowInfo, p: PluginState) 68 | &priority=-5 69 | { 70 | local ip = subnet_to_addr(r$entity$ip); 71 | Scan::log_reporter(fmt("acld_rule_timeout: Rule: %s, %s", subnet_to_addr( 72 | r$entity$ip), r), 1); 73 | } 74 | 75 | @endif 76 | #event NetControl::acld_rule_added(id: count, r: Rule, msg: string) 77 | #{ 78 | # Scan::log_reporter(fmt ("acld_rule_removed: id: %s, Rule: %s", id, r$entity$ip),0); 79 | #} 80 | # 81 | #event NetControl::acld_rule_removed(id: count, r: Rule, msg: string) 82 | #{ 83 | # Scan::log_reporter(fmt ("acld_rule_removed: id: %s, Rule: %s", id, r$entity$ip),0); 84 | #} 85 | # 86 | #event NetControl::acld_rule_error(id: count, r: Rule, msg: string) 87 | #{ 88 | # Scan::log_reporter(fmt ("acld_rule_removed: id: %s, Rule: %s", id, r$entity$ip),0); 89 | #} 90 | -------------------------------------------------------------------------------- /scripts/port-address-scan.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | redef enum Notice::Type += { 5 | PortScan, 6 | AddressScan, 7 | }; 8 | 9 | global service_udp_requires_response = T; 10 | global port_scanner: table[addr] of table[addr] of table[port] of count 11 | &read_expire=1 days; 12 | global check_port_scan: function(orig: addr, resp: addr, svc: port); 13 | 14 | const threshold: vector of count = { 15 | 30, 16 | 100, 17 | 500, 18 | 1000, 19 | 2000, 20 | 10000, 21 | 20000, 22 | 10000, 23 | 15000, 24 | 20000, 25 | 50000, 26 | 100000, 27 | } &redef; 28 | 29 | global port_idx: table[addr, addr] of count &default=0 &read_expire=1 day &redef; 30 | 31 | global ip_idx: table[addr] of count &default=0 &read_expire=1 day &redef; 32 | 33 | global udp_ip_port_idx: table[addr, port] of count &default=0 &read_expire=1 day &redef; 34 | } 35 | 36 | function check_addresscan_thresh(v: vector of count, idx: table[addr] of count, 37 | orig: addr, n: count): bool 38 | { 39 | if ( idx[orig] < |v| && n >= v[idx[orig]] ) { 40 | ++idx[orig]; 41 | return ( T ); 42 | } else 43 | return ( F ); 44 | } 45 | 46 | function check_portscan_thresh(v: vector of count, idx: table[addr, addr] of count, 47 | orig: addr, resp: addr, n: count): bool 48 | { 49 | if ( idx[orig, resp] < |v| && n >= v[idx[orig, resp]] ) { 50 | ++idx[orig, resp]; 51 | return ( T ); 52 | } else 53 | return ( F ); 54 | } 55 | 56 | function has_active_service(c: connection): bool 57 | { 58 | local proto = get_port_transport_proto(c$id$resp_p); 59 | 60 | switch ( proto ) { 61 | case tcp: 62 | # Not a service unless the TCP server did a handshake (SYN+ACK). 63 | if ( c$resp$state == TCP_ESTABLISHED 64 | || c$resp$state == TCP_CLOSED 65 | || c$resp$state == TCP_PARTIAL 66 | || /h/ in c$history ) 67 | return T; 68 | return F; 69 | case udp: 70 | # Not a service unless UDP server has sent something (or the option 71 | # to not care about that is set). 72 | if ( Scan::service_udp_requires_response ) 73 | return c$resp$state == UDP_ACTIVE; 74 | return T; 75 | case icmp: 76 | # ICMP is not considered a service. 77 | return F; 78 | default: 79 | # Unknown/other transport not considered a service for now. 80 | return F; 81 | } 82 | } 83 | 84 | event connection_state_remove(c: connection) 85 | { 86 | local orig = c$id$orig_h; 87 | local resp = c$id$resp_h; 88 | local svc = c$id$resp_p; 89 | 90 | if ( ! has_active_service(c) ) 91 | check_port_scan(orig, resp, svc); 92 | } 93 | 94 | event udp_request(u: connection) 95 | { 96 | local orig = u$id$orig_h; 97 | local resp = u$id$resp_h; 98 | local svc = u$id$resp_p; 99 | 100 | if ( ! has_active_service(u) ) 101 | check_port_scan(orig, resp, svc); 102 | } 103 | 104 | function check_port_scan(orig: addr, resp: addr, svc: port) 105 | { 106 | if ( orig !in port_scanner ) 107 | port_scanner[orig] = table(); 108 | 109 | if ( resp !in port_scanner[orig] ) 110 | port_scanner[orig][resp] = table(); 111 | 112 | if ( svc !in port_scanner[orig][resp] ) 113 | port_scanner[orig][resp][svc] = 0; 114 | 115 | port_scanner[orig][resp][svc] += 1; 116 | 117 | local n = |port_scanner[orig][resp]|; 118 | 119 | local check_thresh = check_portscan_thresh(threshold, port_idx, orig, resp, n); 120 | 121 | if ( check_thresh ) { 122 | NOTICE([ 123 | $note=Scan::PortScan, 124 | $src=orig, 125 | $p=svc, 126 | $n=n, 127 | $msg=fmt("%s has scanned %d ports of %s", orig, n, resp)]); 128 | } 129 | 130 | n = |port_scanner[orig]|; 131 | 132 | local check_add_thresh = check_addresscan_thresh(threshold, ip_idx, orig, n); 133 | 134 | if ( check_add_thresh ) { 135 | local port_list: set[port]; 136 | 137 | for ( r in port_scanner[orig] ) 138 | for ( p in port_scanner[orig][r] ) 139 | add port_list[p]; 140 | 141 | local pl = "["; 142 | for ( pp in port_list ) 143 | pl += fmt("%s ", pp); 144 | pl += "]"; 145 | 146 | NOTICE([ 147 | $note=Scan::AddressScan, 148 | $src=orig, 149 | $n=n, 150 | $msg=fmt("%s has scanned %d hosts on [%s] ports", orig, n, |port_list|)]); 151 | } 152 | } 153 | 154 | event zeek_done() 155 | { 156 | return; 157 | 158 | for ( s in port_scanner ) 159 | for ( d in port_scanner[s] ) 160 | print fmt("%s scanned %s on %s ports", s, d, |port_scanner[s][d]|); 161 | #for (p in port_scanner[s][d]) 162 | #print fmt("%s %s", p, port_scanner[s][d][p]); 163 | } 164 | -------------------------------------------------------------------------------- /scripts/port-flux-density.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | redef enum Notice::Type += { 5 | ScanSpike, 6 | }; 7 | 8 | #global concurrent_scanners_per_port: table[port] of set[addr] &write_expire=6 hrs ; 9 | 10 | global concurrent_scanners_per_port: table[port] of opaque of cardinality 11 | &default=function(n: any): opaque of cardinality { 12 | return hll_cardinality_init(0.1, 0.99); 13 | } &create_expire=100 days; 14 | 15 | global flux_density_idx: table[port] of count &create_expire=7 days; 16 | global flux_density_threshold: vector of count = { 17 | 50, 18 | 100, 19 | 250, 20 | 25000, 21 | 50000, 22 | 75000, 23 | 100000, 24 | 200000, 25 | 250000 26 | }; 27 | 28 | global check_flux_density_threshold: function(v: vector of count, idx: table[port] of count, 29 | orig: port, n: count): bool; 30 | 31 | global check_port_flux_density: function(p: port, a: addr): count; 32 | } 33 | 34 | function check_flux_density_threshold(v: vector of count, idx: table[port] of count, 35 | orig: port, n: count): bool 36 | { 37 | if ( orig !in idx ) 38 | idx[orig] = 0; 39 | # print fmt ("orig: %s and IDX_orig: %s and n is: %s and v[idx[orig]] is: %s", orig, idx[orig], n, v[idx[orig]]); 40 | 41 | if ( idx[orig] < |v| && n >= v[idx[orig]] ) { 42 | ++idx[orig]; 43 | 44 | return ( T ); 45 | } else 46 | return ( F ); 47 | } 48 | 49 | function check_port_flux_density(d_port: port, ip: addr): count 50 | { 51 | if ( ip in Site::local_nets ) 52 | return 0; 53 | 54 | if ( d_port !in concurrent_scanners_per_port ) { 55 | local cp: opaque of cardinality = hll_cardinality_init(0.1, 0.99); 56 | concurrent_scanners_per_port[d_port] = cp; 57 | } 58 | 59 | hll_cardinality_add(concurrent_scanners_per_port[d_port], ip); 60 | 61 | local d_val = double_to_count(hll_cardinality_estimate( 62 | concurrent_scanners_per_port[d_port])); 63 | 64 | local result = check_flux_density_threshold(flux_density_threshold, 65 | flux_density_idx, d_port, d_val); 66 | 67 | if ( result ) { 68 | local msg = fmt("%s has huge spike with %s uniq scanners", d_port, d_val); 69 | #print fmt ("%s", msg); 70 | #NOTICE([$note=ScanSpike, $p=d_port, $src_peer=get_event_peer()$descr, $msg=msg]); 71 | #NOTICE([$note=ScanSpike, $n=d_val, $p=d_port, $msg=msg]); 72 | 73 | } 74 | 75 | return d_val; 76 | } 77 | -------------------------------------------------------------------------------- /scripts/port-scan.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | const activate_PortScan = T &redef; 5 | const activate_LowPortTrolling = T &redef; 6 | 7 | redef enum Notice::Type += { 8 | PortScan, # the source has scanned a number of ports 9 | PortScanSummary, # summary of distinct ports per scanner 10 | }; 11 | 12 | const port_summary_trigger = 20 &redef; 13 | 14 | #global port_summary: 15 | # function(t: table[addr] of set[port], orig: addr): interval; 16 | 17 | #global distinct_ports: table[addr] of set[port] 18 | # &read_expire = 1 days &expire_func=port_summary &redef; 19 | 20 | #global lowport_summary: 21 | # function(t: table[addr] of set[port], orig: addr): interval; 22 | #global distinct_low_ports: table[addr] of set[port] 23 | # &read_expire = 1 days &expire_func=lowport_summary &redef; 24 | 25 | #const port_summary_trigger = 20 &redef; 26 | #const lowport_summary_trigger = 10 &redef; 27 | 28 | # Indexed by scanner address, yields a table with scanned hosts 29 | # (and ports). 30 | #global scan_triples: table[addr] of table[addr] of set[port]; 31 | 32 | # Report a scan of ports at each of these points. 33 | #const report_port_scan: vector of count = { 34 | # 50, 250, 1000, 5000, 10000, 25000, 65000, 35 | #} &redef; 36 | 37 | # Once a source has scanned this many different ports (to however many 38 | # different remote hosts), start tracking its per-destination access. 39 | #const possible_port_scan_thresh = 20 &redef; 40 | 41 | # Threshold for scanning privileged ports. 42 | #const priv_scan_trigger = 5 &redef; 43 | #const troll_skip_service = { 44 | # 25/tcp, 21/tcp, 22/tcp, 20/tcp, 80/tcp, 443/tcp, 45 | #} &redef; 46 | 47 | #global remove_possible_source: 48 | # function(s: set[addr], idx: addr): interval; 49 | #global possible_scan_sources: set[addr] 50 | #&expire_func=remove_possible_source &read_expire = 1 days; 51 | 52 | global check_PortScan: function(c: connection, established: bool, 53 | reverse: bool); 54 | } 55 | 56 | @if ( Cluster::is_enabled() ) 57 | export { 58 | global Scan::m_w_portscan_update_known_scanners: event(orig: addr); 59 | global Scan::w_m_portscan_new: event(orig: addr, d_port: port, resp: addr, 60 | outbound: bool); 61 | global Scan::add_to_portscan_cache: function(orig: addr, d_port: port, 62 | resp: addr); 63 | } 64 | @endif 65 | 66 | function port_summary(t: table[addr] of set[port], orig: addr): interval 67 | { 68 | local num_distinct_ports = orig in t ? |t[orig]| : 0; 69 | 70 | if ( num_distinct_ports >= port_summary_trigger ) 71 | NOTICE([ 72 | $note=PortScanSummary, 73 | $src=orig, 74 | $n=num_distinct_ports, 75 | $msg=fmt("%s scanned a total of %d ports", orig, num_distinct_ports)]); 76 | 77 | return 0 secs; 78 | } 79 | 80 | function lowport_summary(t: table[addr] of set[port], orig: addr): interval 81 | { 82 | local num_distinct_lowports = orig in t ? |t[orig]| : 0; 83 | 84 | if ( num_distinct_lowports >= lowport_summary_trigger ) 85 | NOTICE([ 86 | $note=LowPortScanSummary, 87 | $src=orig, 88 | $n=num_distinct_lowports, 89 | $msg=fmt("%s scanned a total of %d low ports", orig, num_distinct_lowports)]); 90 | 91 | return 0 secs; 92 | } 93 | 94 | function check_lowporttrolling(orig: addr, service: port, resp: addr): bool 95 | { 96 | local troll = F; 97 | if ( orig !in distinct_low_ports || service !in distinct_low_ports[orig] ) { 98 | if ( orig !in distinct_low_ports ) 99 | distinct_low_ports[orig] = set(); 100 | 101 | add distinct_low_ports[orig][service]; 102 | 103 | if ( |distinct_low_ports[orig]| == priv_scan_trigger 104 | && orig !in Site::neighbor_nets ) { 105 | #local s = service in port_names ? port_names[service] : fmt("%s", service); 106 | local s = fmt("%s", service); 107 | 108 | local svrc_msg = fmt("low port trolling %s %s", orig, s); 109 | NOTICE([ 110 | $note=LowPortTrolling, 111 | $src=orig, 112 | $src_peer=get_local_event_peer(), 113 | $p=service, 114 | $msg=svrc_msg]); 115 | 116 | troll = T; 117 | } 118 | 119 | if ( ignore_scanners_threshold > 0 120 | && |distinct_low_ports[orig]| > ignore_scanners_threshold ) 121 | ignore_addr(orig); 122 | } 123 | 124 | return troll; 125 | } 126 | 127 | function check_portscan_thresh(orig: addr, service: port, resp: addr): bool 128 | { 129 | if ( orig !in scan_triples ) 130 | scan_triples[orig] = table(); 131 | 132 | if ( resp !in scan_triples[orig] ) 133 | scan_triples[orig][resp] = set(); 134 | 135 | if ( service !in scan_triples[orig][resp] ) { 136 | add scan_triples[orig][resp][service]; 137 | 138 | if ( thresh_check_2(report_port_scan, rpts_idx, orig, resp, 139 | |scan_triples[orig][resp]|) ) { 140 | local m = |scan_triples[orig][resp]|; 141 | NOTICE([ 142 | $note=PortScan, 143 | $n=m, 144 | $src=orig, 145 | $p=service, 146 | $src_peer=get_local_event_peer(), 147 | $msg=fmt("%s has scanned %d ports of %s", orig, m, resp)]); 148 | return T; 149 | } 150 | } 151 | 152 | return F; 153 | } 154 | 155 | function check_PortScan(c: connection, established: bool, reverse: bool) 156 | { 157 | if ( ! activate_PortScan ) 158 | return; 159 | 160 | #if ( suppress_scan_checks ) 161 | # return ; 162 | 163 | if ( established ) { 164 | # Don't consider established connections for port scanning, 165 | # it's too easy to be mislead by FTP-like applications that 166 | # legitimately gobble their way through the port space. 167 | return; 168 | 169 | local id = c$id; 170 | 171 | local service = "ftp-data" in c$service ? 20/tcp : ( reverse ? id$orig_p : 172 | id$resp_p ); 173 | local rev_service = reverse ? id$resp_p : id$orig_p; 174 | local orig = reverse ? id$resp_h : id$orig_h; 175 | local resp = reverse ? id$orig_h : id$resp_h; 176 | local outbound = Site::is_local_addr(orig); 177 | 178 | #if (orig in Scan::known_scanners) 179 | if ( Scan::known_scanners[orig]$status ) 180 | return; 181 | 182 | # Coarse search for port-scanning candidates: those that have made 183 | # connections (attempts) to possible_port_scan_thresh or more 184 | # distinct ports. 185 | if ( orig !in distinct_ports || service !in distinct_ports[orig] ) { 186 | if ( orig !in distinct_ports ) 187 | distinct_ports[orig] = set(); 188 | 189 | if ( service !in distinct_ports[orig] ) 190 | add distinct_ports[orig][service]; 191 | 192 | if ( |distinct_ports[orig]| >= possible_port_scan_thresh 193 | && orig !in scan_triples ) { 194 | scan_triples[orig] = table(); 195 | add possible_scan_sources[orig]; 196 | } 197 | } 198 | 199 | # Check for low ports. 200 | if ( activate_LowPortTrolling 201 | && ! outbound 202 | && service < 1024/tcp 203 | && service !in troll_skip_service ) { 204 | local troll_result = check_lowporttrolling(orig, service, resp); 205 | } 206 | 207 | # For sources that have been identified as possible scan sources, 208 | # keep track of per-host scanning. 209 | if ( orig in possible_scan_sources ) { 210 | local thresh_result = check_portscan_thresh(orig, service, resp); 211 | } 212 | 213 | @if ( Cluster::is_enabled() ) 214 | local _msg = fmt(" add_to_likely_scanner: calling w_m_portscan_new for %s, %s, %s", orig, service, resp); 215 | log_reporter(_msg, 0); 216 | 217 | event Scan::w_m_portscan_new(orig, service, resp, outbound); 218 | @endif 219 | } # end if established 220 | } 221 | 222 | @if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) 223 | event Scan::w_m_portscan_new(orig: addr, service: port, resp: addr, 224 | outbound: bool) 225 | { 226 | local msg = fmt(" inside w_m_portscan_new for %s, %s, %s", orig, service, resp); 227 | log_reporter(msg, 0); 228 | 229 | #if ( orig in Scan::known_scanners) 230 | if ( Scan::known_scanners[orig]$status ) { 231 | Scan::known_scanners[orig]$status = T; 232 | return; 233 | } 234 | 235 | if ( orig !in distinct_ports || service !in distinct_ports[orig] ) { 236 | if ( orig !in distinct_ports ) 237 | distinct_ports[orig] = set(); 238 | 239 | if ( service !in distinct_ports[orig] ) 240 | add distinct_ports[orig][service]; 241 | 242 | if ( |distinct_ports[orig]| >= possible_port_scan_thresh 243 | && orig !in scan_triples ) { 244 | scan_triples[orig] = table(); 245 | add possible_scan_sources[orig]; 246 | } 247 | } 248 | 249 | # Check for low ports. 250 | if ( activate_LowPortTrolling 251 | && ! outbound 252 | && service < 1024/tcp 253 | && service !in troll_skip_service ) { 254 | local troll_result = check_lowporttrolling(orig, service, resp); 255 | } 256 | 257 | # For sources that have been identified as possible scan sources, 258 | # keep track of per-host scanning. 259 | if ( orig in possible_scan_sources ) { 260 | local thresh_result = check_portscan_thresh(orig, service, resp); 261 | } 262 | 263 | if ( troll_result || thresh_result ) { 264 | local _msg = fmt("w_m_portscan_new: calling m_w_portscan_update_known_scanners for: %s, %s, %s", orig, service, resp); 265 | log_reporter(_msg, 0); 266 | 267 | event Scan::m_w_portscan_update_known_scanners(orig); 268 | 269 | if ( orig !in Scan::known_scanners ) { 270 | local ss: scan_stats; 271 | local hh: set[addr]; 272 | Scan::known_scanners[orig] = ss; 273 | Scan::known_scanners[orig]$hosts = hh; 274 | known_scanners[orig]$scanner = orig; 275 | 276 | Scan::known_scanners[orig]$status = T; 277 | } 278 | } 279 | } 280 | @endif 281 | 282 | # we can get away with only sending orig here since thats what is used to update 283 | # known_scanners table on workers , we are still sending d_port, resp 284 | # for debugging assurances 285 | 286 | @if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) 287 | event Scan::m_w_portscan_update_known_scanners(orig: addr) 288 | { 289 | if ( orig !in Scan::known_scanners ) 290 | Scan::known_scanners[orig]$status = T; 291 | 292 | local msg = fmt( 293 | "portscan: added m_w_portscan_update_known_scanners for: %s, %s, %s", orig, 294 | Scan::known_scanners[orig], |Scan::known_scanners[orig]|); 295 | log_reporter(msg, 0); 296 | } 297 | @endif 298 | # events for scan detections 299 | 300 | #event connection_established(c: connection) 301 | # { 302 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 303 | # Scan::check_PortScan(c, T, is_reverse_scan); 304 | # 305 | # #local trans = get_port_transport_proto(c$id$orig_p); 306 | # #if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 307 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 308 | # } 309 | # 310 | #event partial_connection(c: connection) 311 | # { 312 | # Scan::check_PortScan(c, T, F); 313 | # } 314 | # 315 | #event connection_attempt(c: connection) 316 | # { 317 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 318 | # Scan::check_PortScan(c, F, is_reverse_scan); 319 | # 320 | # #local trans = get_port_transport_proto(c$id$orig_p); 321 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 322 | # # TRW::check_TRW_scan(c, conn_state(c, trans), F); 323 | # } 324 | # 325 | #event connection_half_finished(c: connection) 326 | # { 327 | # # Half connections never were "established", so do scan-checking here. 328 | # Scan::check_PortScan(c, F, F); 329 | # } 330 | # 331 | #event connection_rejected(c: connection) 332 | # { 333 | # local is_reverse_scan = (c$orig$state == TCP_RESET && c$id$resp_p !in likely_server_ports); 334 | # 335 | # Scan::check_PortScan(c, F, is_reverse_scan); 336 | # 337 | # #local trans = get_port_transport_proto(c$id$orig_p); 338 | # #if ( trans == tcp && TRW::use_TRW_algorithm ) 339 | # # TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 340 | # } 341 | # 342 | #event connection_reset(c: connection) 343 | # { 344 | # if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE ) 345 | # { 346 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE && c$id$resp_p !in likely_server_ports); 347 | # # We never heard from one side - that looks like a scan. 348 | # Scan::check_PortScan(c, c$orig$size + c$resp$size > 0, is_reverse_scan); 349 | # } 350 | # } 351 | # 352 | #event connection_pending(c: connection) 353 | # { 354 | # if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE ) 355 | # Scan::check_PortScan(c, F, F); 356 | # } 357 | # 358 | # 359 | -------------------------------------------------------------------------------- /scripts/scan-base.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | redef Config::config_files += { 5 | "/YURT/feeds/zeek-FP/Scan::scan-config.zeek" 6 | }; 7 | 8 | # List of well known local server/ports to exclude for scanning 9 | # purposes. skips 10 | option skip_services: set[port] = { } &redef; 11 | option skip_outbound_services: set[port] = { } &redef; 12 | option skip_scan_nets: set[subnet] = { } &redef; 13 | option skip_dest_server_ports: set[addr, port] = { } &redef; 14 | option skip_scan_sources: set[addr] = { #255.255.255.255, # who knows why we see these, but we do 15 | } &redef; 16 | 17 | 18 | option ignore_hot_subnets: set[subnet] = { [2620:0:28B0::]/44,} ; 19 | option ignore_hot_subnets_ports: set[port] = { 53/tcp, 853/tcp,}; 20 | 21 | global Scan::add_to_known_scanners: function(orig: addr, detect: string); 22 | global Scan::enable_scan_summary = T &redef; 23 | global Scan::use_catch_n_release = T &redef; 24 | global enable_big_tables = F &redef; 25 | 26 | redef enum Notice::Type += { 27 | PasswordGuessing, # source tried many user/password combinations 28 | SuccessfulPasswordGuessing, # same, but a login succeeded 29 | DisableCatchRelease, 30 | }; 31 | 32 | type scan_info: record { 33 | scanner: addr &log; 34 | status: bool &default=F; 35 | #sport: port &log &optional ; 36 | detection: string &log &optional &default=""; 37 | detect_ts: time &default=double_to_time(0.0); 38 | event_peer: string &log &optional; 39 | expire: bool &default=F; 40 | }; 41 | 42 | # we let only the manager manage deletion of the known_scanners on the worker 43 | # Reason: (i) we don't know separate timers for workers and managers for a scanner 44 | # (ii) unexpected absence of known_scanners can cause values to be wrong in scan_summary 45 | 46 | global finish_scan_summary: event(ip: addr); 47 | 48 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) || ! Cluster::is_enabled() ) 49 | global known_scanners_inactive: function(t: table[addr] of scan_info, 50 | idx: addr): interval; 51 | const known_scanners_create_expire: interval = 1 day; # 20 mins ; 52 | global known_scanners: table[addr] of scan_info 53 | &read_expire=known_scanners_create_expire 54 | &expire_func=known_scanners_inactive; 55 | @endif 56 | 57 | # workers will keep known_scanners until manager sends m_w_remove_scanner event 58 | # when manager calls known_scanners_inactive event 59 | 60 | @if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) 61 | global known_scanners: table[addr] of scan_info; 62 | @endif 63 | 64 | type conn_info: record { 65 | cid: conn_id; 66 | ts: time; 67 | }; 68 | 69 | # used to identify when a scan started and how many hosts touched before detection 70 | type start_ts: record { 71 | ts: time &default=double_to_time(0.0); 72 | conn_count: count &default=0; 73 | }; 74 | 75 | global is_scanner: function(cid: conn_id): bool; 76 | global is_darknet: function(ip: addr): bool; 77 | global table_start_ts: table[addr] of start_ts &read_expire=1 hrs; 78 | global ignored_scanners: set[addr] &create_expire=1 day &redef; 79 | 80 | # helper functions 81 | global is_failed: function(c: connection): bool; 82 | global is_reverse_failed: function(c: connection): bool; 83 | global print_state: function(s: count, t: transport_proto): string; 84 | 85 | global ignore_addr: function(a: addr); 86 | global clear_addr: function(a: addr); 87 | global dont_drop: function(a: addr): bool; 88 | 89 | global can_drop_connectivity = F &redef; 90 | global dont_drop_locals = T &redef; 91 | global is_catch_release_active: function(ip: addr): bool; 92 | 93 | const never_drop_nets: set[subnet] &redef; 94 | 95 | # TODO: Whether to consider UDP "connections" for scan detection. 96 | # Can lead to false positives due to UDP fanout from some P2P apps. 97 | const suppress_UDP_scan_checks = F &redef; 98 | global whitelist_subnet: set[subnet] &backend=Broker::MEMORY; 99 | global whitelist: set[addr] &backend=Broker::MEMORY; 100 | } # end of export 101 | 102 | export { 103 | global Scan::m_w_add_scanner: event(ss: scan_info); 104 | global Scan::potential_scanner: event(ci: conn_info, established: bool, 105 | reverse: bool, filtrator: string); 106 | global Scan::m_w_update_scanner: event(ip: addr, status_flag: bool); 107 | global Scan::w_m_update_scanner: event(ss: scan_info); 108 | global Scan::m_w_remove_scanner: event(ip: addr); 109 | 110 | global get_subnet:function (ip: addr):subnet; 111 | 112 | } 113 | 114 | #@if ( Cluster::is_enabled() ) 115 | #@load base/frameworks/cluster 116 | #redef Cluster::manager2worker_events += /Scan::m_w_(add|remove|update)_scanner/; 117 | #redef Cluster::worker2manager_events += /Scan::w_m_(new|add|remove|update)_scanner/; 118 | #@endif 119 | 120 | 121 | function get_subnet(ip: addr):subnet 122 | { 123 | local scanner_subnet : subnet; 124 | 125 | if (is_v6_addr(ip)) 126 | scanner_subnet = mask_addr(ip, 64); 127 | else 128 | scanner_subnet = mask_addr(ip, 24); 129 | 130 | return scanner_subnet; 131 | } 132 | 133 | 134 | @if ( Cluster::is_enabled() ) 135 | 136 | @if ( Cluster::local_node_type() == Cluster::MANAGER ) 137 | event zeek_init() 138 | { 139 | Broker::auto_publish(Cluster::worker_topic, Scan::m_w_add_scanner); 140 | Broker::auto_publish(Cluster::worker_topic, Scan::m_w_remove_scanner); 141 | Broker::auto_publish(Cluster::worker_topic, Scan::m_w_update_scanner); 142 | } 143 | @else 144 | event zeek_init() 145 | { 146 | Broker::auto_publish(Cluster::manager_topic, Scan::potential_scanner); 147 | Broker::auto_publish(Cluster::manager_topic, Scan::w_m_update_scanner); 148 | } 149 | @endif 150 | 151 | @endif 152 | 153 | # Checks if a perticular connection is already blocked and managed by netcontrol 154 | # and catch-and-release. If yes, we don't process this connection any-further in the 155 | # scan-detection module 156 | # 157 | # ip: addr - ip address which needs to be checked 158 | # 159 | # Returns: bool - returns T or F depending if IP is managed by :bro:see:`NetControl::get_catch_release_info` 160 | 161 | function is_catch_release_active(ip: addr): bool 162 | { 163 | #if (gather_statistics) 164 | # s_counters$is_catch_release_active += 1; 165 | 166 | @ifdef ( NetControl::BlockInfo ) 167 | local orig = ip; 168 | 169 | local bi: NetControl::BlockInfo; 170 | bi = NetControl::get_catch_release_info(orig); 171 | 172 | #log_reporter(fmt("is_catch_release_active: blockinfo is %s, %s", cid, bi),0); 173 | # if record bi is initialized 174 | if ( bi$watch_until != 0.0 ) 175 | return T; 176 | 177 | # means empty bi 178 | # [block_until=, watch_until=0.0, num_reblocked=0, current_interval=0, current_block_id=] 179 | 180 | @endif 181 | 182 | return F; 183 | } 184 | 185 | function dont_drop(a: addr): bool 186 | { 187 | return ! can_drop_connectivity 188 | || a in never_drop_nets 189 | || ( dont_drop_locals && Site::is_local_addr(a) ); 190 | } 191 | 192 | function is_darknet(ip: addr): bool 193 | { 194 | if ( Scan::SubnetCountToActivteLandMine != |Scan::subnet_table| ) 195 | return F; 196 | 197 | if ( Site::is_local_addr(ip) && ip in Scan::allocated_cache ) 198 | return F; 199 | 200 | if ( Site::is_local_addr(ip) && ip !in Scan::subnet_table ) 201 | return T; 202 | 203 | return F; 204 | } 205 | 206 | # action to take when scanner is expiring 207 | 208 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) || ! Cluster::is_enabled() ) 209 | function known_scanners_inactive(t: table[addr] of scan_info, idx: addr) 210 | : interval 211 | { 212 | #log_reporter(fmt("known_scanners_inactive: %s", t[idx]),0); 213 | 214 | # sending message to all workers to delete this scanner 215 | # since its inactive now 216 | 217 | event Scan::m_w_remove_scanner(idx); 218 | schedule 30 secs { Scan::finish_scan_summary(idx) }; 219 | 220 | # delete from the manager too 221 | 222 | return 0 secs; 223 | } 224 | @endif 225 | 226 | function ignore_addr(a: addr) 227 | { 228 | clear_addr(a); 229 | add ignored_scanners[a]; 230 | } 231 | 232 | function clear_addr(a: addr) 233 | { 234 | log_reporter(fmt("scan-base: clear_addr : %s", a), 0); 235 | 236 | #if (a in known_scanners) 237 | #{ 238 | #Scan::log_reporter(fmt ("deleted: known_scanner: %s, %s", a, Scan::known_scanners[a]),1); 239 | #event Scan::w_m_update_known_scan_stats(a, known_scanners[a]); 240 | #delete known_scanners[a]; 241 | #} 242 | 243 | #if (a in distinct_peers) 244 | # delete distinct_peers[a]; 245 | 246 | #if (a in shut_down_thresh_reached) 247 | # delete shut_down_thresh_reached[a]; 248 | 249 | #if (a in backscatter) 250 | # delete backscatter[a]; 251 | 252 | #if (a in distinct_backscatter_peers) 253 | # delete distinct_backscatter_peers[a]; 254 | 255 | #if (a in likely_scanner) 256 | # delete likely_scanner[a] ; 257 | 258 | #if ( a in landmine_distinct_peers) 259 | # delete landmine_distinct_peers[a] ; 260 | 261 | #if (a in distinct_ports) 262 | # delete distinct_ports[a]; 263 | 264 | #if (a in distinct_low_ports) 265 | # delete distinct_low_ports[a]; 266 | 267 | #if (a in scan_triples) 268 | # delete scan_triples[a]; 269 | 270 | #if (a in rb_idx) 271 | # delete rb_idx[a]; 272 | #if (a in rps_idx) 273 | # delete rps_idx[a]; 274 | #if (a in rops_idx) 275 | # delete rops_idx[a]; 276 | #if (a in rat_idx) 277 | # delete rat_idx[a]; 278 | #if (a in rrat_idx) 279 | # delete rrat_idx[a]; 280 | 281 | # delete possible_scan_source[a]; 282 | # delete pre_distinct_peers[a]; 283 | # delete ignored_scanners[a]; 284 | } 285 | 286 | function is_failed(c: connection): bool 287 | { 288 | # Sr || ( (hR || ShR) && (data not sent in any direction) ) 289 | if ( ( c$orig$state == TCP_SYN_SENT && c$resp$state == TCP_RESET ) 290 | || ( c$orig$state == TCP_SYN_SENT && c$resp$state == TCP_INACTIVE ) 291 | || ( ( ( 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 ) ) 292 | return T; 293 | return F; 294 | } 295 | 296 | function is_reverse_failed(c: connection): bool 297 | { 298 | # reverse scan i.e. conn dest is the scanner 299 | # sR || ( (Hr || sHr) && (data not sent in any direction) ) 300 | if ( ( c$resp$state == TCP_SYN_SENT && c$orig$state == TCP_RESET ) 301 | || ( ( ( 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 ) ) 302 | return T; 303 | return F; 304 | } 305 | 306 | function print_state(s: count, t: transport_proto): string 307 | { 308 | if ( t == tcp ) { 309 | switch ( s ) { 310 | case 0: 311 | return "TCP_INACTIVE"; 312 | case 1: 313 | return "TCP_SYN_SENT"; 314 | case 2: 315 | return "TCP_SYN_ACK_SENT"; 316 | case 3: 317 | return "TCP_PARTIAL"; 318 | case 4: 319 | return "TCP_ESTABLISHED"; 320 | case 5: 321 | return "TCP_CLOSED"; 322 | case 6: 323 | return "TCP_RESET"; 324 | } 325 | ; 326 | } 327 | 328 | if ( t == udp ) { 329 | switch ( s ) { 330 | case 0: 331 | return "UDP_INACTIVE"; 332 | case 1: 333 | return "UDP_ACTIVE"; 334 | } 335 | } 336 | 337 | return "UNKNOWN"; 338 | } 339 | 340 | event table_sizes() 341 | { 342 | return; 343 | 344 | #log_reporter(fmt("table_size: backscatter: %s",|backscatter|),0); 345 | #log_reporter(fmt("table_size: conn_table: %s",|conn_table|),0); 346 | #log_reporter(fmt("table_size: distinct_backscatter_peers: %s",|distinct_backscatter_peers|),0); 347 | #log_reporter(fmt("table_size: distinct_low_ports: %s",|distinct_low_ports|),0); 348 | #log_reporter(fmt("table_size: distinct_peers: %s",|distinct_peers|),0); 349 | #log_reporter(fmt("table_size: distinct_ports: %s",|distinct_ports|),0); 350 | #log_reporter(fmt("table_size: done_with: %s",|done_with|),0); 351 | #log_reporter(fmt("table_size: expire_done_with: %s",|expire_done_with|),0); 352 | #log_reporter(fmt("table_size: host_profiles: %s",|Site::host_profiles|),0); 353 | #log_reporter(fmt("table_size: known_scanners: %s",|known_scanners|),0); 354 | #log_reporter(fmt("table_size: known_scanners_inactive: %s",|known_scanners_inactive|),0); 355 | #log_reporter(fmt("table_size: landmine_distinct_peers: %s",|landmine_distinct_peers|),0); 356 | #log_reporter(fmt("table_size: likely_scanner: %s",|likely_scanner|),0); 357 | #log_reporter(fmt("table_size: rat_idx: %s",|rat_idx|),0); 358 | #log_reporter(fmt("table_size: rb_idx: %s",|rb_idx|),0); 359 | #log_reporter(fmt("table_size: report_conn_stats: %s",|report_conn_stats|),0); 360 | #log_reporter(fmt("table_size: rops_idx: %s",|rops_idx|),0); 361 | #log_reporter(fmt("table_size: rps_idx: %s",|rps_idx|),0); 362 | #log_reporter(fmt("table_size: rpts_idx: %s",|rpts_idx|),0); 363 | #log_reporter(fmt("table_size: rrat_idx: %s",|rrat_idx|),0); 364 | #log_reporter(fmt("table_size: scan_triples: %s",|scan_triples|),0); 365 | #log_reporter(fmt("table_size: shut_down_thresh_reached: %s",|shut_down_thresh_reached|),0); 366 | #log_reporter(fmt("table_size: subnet_table: %s",|Site::subnet_table|),0); 367 | #log_reporter(fmt("table_size: thresh_check: %s",|thresh_check|),0); 368 | #log_reporter(fmt("table_size: uid_table: %s",|uid_table|),0); 369 | #log_reporter(fmt("table_size: whitelist_ip_table: %s",|whitelist_ip_table|),0); 370 | #log_reporter(fmt("table_size: whitelist_subnet_table: %s",|whitelist_subnet_table|),0); 371 | 372 | schedule 10 mins { table_sizes() }; 373 | } 374 | -------------------------------------------------------------------------------- /scripts/scan-blocked-hotsubnets.zeek: -------------------------------------------------------------------------------- 1 | module Scan ; 2 | 3 | #redef exit_only_after_terminate = T ; 4 | 5 | export { 6 | 7 | redef enum Notice::Type += { 8 | HotSubnet, # Too many scanners originating from this subnet 9 | BlocknetsIP, 10 | BlocknetsFileReadFail, 11 | }; 12 | 13 | 14 | # T/feeds/BRO-feeds/WIRED.blocknet 15 | # header info 16 | #fields NETWORK BLOCK_FLAVOR 17 | #2.187.44.0/24 blocknet 18 | #2.187.45.0/24 blocknet 19 | 20 | type blocknet_Idx: record { 21 | NETWORK: subnet; 22 | }; 23 | 24 | type blocknet_Val: record { 25 | NETWORK: subnet; 26 | BLOCK_FLAVOR: string &optional ; 27 | }; 28 | 29 | global blocked_nets: table[subnet] of blocknet_Val = table() &redef ; 30 | global blocknet_feed="/feeds/BRO-feeds/WIRED.blocknet" &redef ; 31 | } 32 | 33 | 34 | export { 35 | 36 | global hot_subnets: table[subnet] of set[addr] &create_expire=7 days; 37 | global hot_subnets_idx: table[subnet] of count &create_expire=7 days; 38 | global hot_subnets_threshold: vector of count = { 3, 10, 25, 100, 200, 255 } ; 39 | 40 | global hot_subnet_check:function(ip: addr); 41 | global check_subnet_threshold: function (v: vector of count, idx: table[subnet] of count, orig: subnet, n: count):bool ; 42 | } 43 | 44 | 45 | # FAILURE-CHECK 46 | # we catch the error in subnet feed is empty and populate blocked_nets with local_nets 47 | # so that LandMine detection doesn't block accidently 48 | 49 | 50 | hook Notice::policy(n: Notice::Info) 51 | { 52 | if ( n$note == Scan::BlocknetsFileReadFail) 53 | { 54 | add n$actions[Notice::ACTION_EMAIL]; 55 | } 56 | } 57 | 58 | # handle this failure 59 | # Reporter::WARNING /feeds/BRO-feeds/WIRED.blocknet.2/Input::READER_ASCII: 60 | # Init: cannot open /feeds/BRO-feeds/WIRED.blocknet.2 (empty) 61 | 62 | event reporter_warning(t: time , msg: string , location: string ) 63 | { 64 | 65 | if (/WIRED.blocknet.*\/Input::READER_ASCII: Init: cannot open/ in msg) 66 | { 67 | NOTICE([$note=BlocknetsFileReadFail, $msg=fmt("%s", msg)]); 68 | } 69 | } 70 | 71 | 72 | event Input::end_of_data(name: string, source: string) 73 | { 74 | 75 | if (/WIRED.blocknet/ in name) 76 | { 77 | print fmt ("name=%s, source=%s, records=%s", name, source, |source|); 78 | } 79 | # since subnet table is zero size 80 | # we poulate with local_nets 81 | 82 | if (|Scan::blocked_nets| == 0) 83 | { 84 | for (nets in Site::local_nets) 85 | { local sv: blocknet_Val ; 86 | blocked_nets[nets] = sv ; 87 | } 88 | } 89 | } 90 | 91 | event Scan::line(description: Input::TableDescription, tpe: Input::Event, left: blocknet_Idx, right: blocknet_Val) 92 | { 93 | local msg: string; 94 | 95 | if ( tpe == Input::EVENT_NEW ) { 96 | #print fmt ("NEW"); 97 | } 98 | 99 | 100 | if (tpe == Input::EVENT_CHANGED) { 101 | #print fmt ("CHANGED"); 102 | } 103 | 104 | 105 | if (tpe == Input::EVENT_REMOVED ) { 106 | #print fmt ("REMOVED"); 107 | 108 | } 109 | } 110 | 111 | 112 | event zeek_init() &priority=10 113 | { 114 | Input::add_table([$source=blocknet_feed, $name="blocked_nets", $idx=blocknet_Idx, $val=blocknet_Val, $destination=blocked_nets, $mode=Input::REREAD, $ev=Scan::line]); 115 | 116 | 117 | # Input::add_table([$source=blocknet_feed, $name="blocked_nets", $idx=blocknet_Idx, $val=blocknet_Val, $destination=blocked_nets, $mode=Input::REREAD]); 118 | # $pred(typ: Input::Event, left: blocknet_Idx, right: blocknet_Val = { left$epo = to_lower(left$epo); return T;) }]); 119 | } 120 | 121 | 122 | event zeek_done() 123 | { 124 | #print fmt("bro-done"); 125 | #print fmt("digested %s records in blocked_nets", |Scan::blocked_nets|); 126 | #print fmt("blocked_nets %s", Scan::blocked_nets); 127 | 128 | } 129 | 130 | 131 | 132 | 133 | function check_subnet_threshold(v: vector of count, idx: table[subnet] of count, orig: subnet, n: count):bool 134 | { 135 | if (orig !in idx) 136 | idx[orig]= 0 ; 137 | 138 | # print fmt ("orig: %s and IDX_orig: %s and n is: %s and v[idx[orig]] is: %s", orig, idx[orig], n, v[idx[orig]]); 139 | 140 | if ( idx[orig] < |v| && n >= v[idx[orig]] ) 141 | { 142 | ++idx[orig]; 143 | 144 | return (T); 145 | } 146 | else 147 | return (F); 148 | } 149 | 150 | function hot_subnet_check(ip: addr) 151 | { 152 | 153 | if (known_scanners[ip]$detection == "BackscatterSeen") 154 | return ; 155 | 156 | 157 | # check for subnet scanners 158 | local scanner_subnet = mask_addr(ip, 24) ; 159 | 160 | if (scanner_subnet !in hot_subnets) 161 | { 162 | local a: set[addr] ; 163 | hot_subnets[scanner_subnet] = a ; 164 | } 165 | 166 | if (ip !in hot_subnets[scanner_subnet] ); 167 | add hot_subnets[scanner_subnet][ip]; 168 | 169 | local n = |hot_subnets[scanner_subnet]| ; 170 | 171 | local result = F ; 172 | result = check_subnet_threshold(hot_subnets_threshold, hot_subnets_idx , scanner_subnet, n); 173 | 174 | # print fmt ("%s has %s scanners originating from it", scanner_subnet, n); 175 | 176 | if (result) 177 | { 178 | local _msg = fmt ("%s has %s scanners originating from it", scanner_subnet, n); 179 | 180 | #NOTICE([$note=HotSubnet, $src_peer=get_local_event_peer(), $src=ip, $msg=fmt("%s", _msg)]); 181 | NOTICE([$note=HotSubnet, $src=ip, $msg=fmt("%s", _msg)]); 182 | } 183 | 184 | if (ip in blocked_nets) 185 | { 186 | local msg = fmt ("%s is Scanner from blocknet %s", ip, blocked_nets[ip]); 187 | NOTICE([$note=BlocknetsIP, $src=ip, $msg=fmt("%s", msg)]); 188 | } 189 | 190 | } 191 | 192 | -------------------------------------------------------------------------------- /scripts/scan-config.zeek: -------------------------------------------------------------------------------- 1 | # Central location to set/calibrate all redef'able variables for scan-detection 2 | # This saves users effort to locate specific knobs deep inside heuristics 3 | # Modify these as you see fit 4 | # Presently left as standard defaults 5 | 6 | module Scan; 7 | 8 | redef Scan::activate_SubnetKnock = T; 9 | redef Scan::activate_KnockKnockScan = T; 10 | redef Scan::activate_Backscatter = T; 11 | redef Scan::activate_LandMine = T; 12 | redef Scan::activate_LowPortTrolling = T; 13 | redef Scan::activate_AddressScan = T; 14 | redef Scan::activate_PortScan = T; 15 | redef TRW::use_TRW_algorithm = F; 16 | 17 | ## Important to configure for Landmine detection 18 | # if subnet_feed is empty then LandMine detection wont work 19 | 20 | redef Scan::landmine_thresh_trigger = 5 &redef; 21 | redef Scan::landmine_ignore_ports: set[port] = { 22 | 53/tcp, 23 | 53/udp, 24 | 1094/tcp, 25 | 1095/tcp, 26 | 27 | }; 28 | 29 | redef Scan::allow_icmp_landmine_check = F; 30 | redef Scan::ignore_landmine_ports: set[port] = { 31 | 8/icmp 32 | } &redef; 33 | 34 | # 8/icmp as d_port == backscatter from DoS 35 | 36 | ## this is list of allocated subnets in your network 37 | ## landmine works on watching connections which are not in allocated subnets 38 | ## file looks as follows (tab seperated) 39 | # Example 40 | # #fields Network Gateway Enclaves Use 41 | # 128.3.2.0/24 128.3.2.1 LBL Research group 42 | 43 | @ifndef ( Scan::subnet_feed ) 44 | @load ./site-subnets.zeek 45 | @endif 46 | 47 | redef Scan::subnet_feed = "/YURT/feeds/BRO-feeds/LBL-subnets.csv-LATEST_BRO"; 48 | 49 | ################ 50 | ## Input files - Whitelist IP and Subnets file 51 | # Example Header and 1st row - whitelist.scan 52 | # #fields ip comment 53 | # 1.2.1.1 a scanning ip addres 54 | # Example Header and 1st row - subnet-whitelist.scan 55 | # #fields nets comment 56 | # 15.5.5.5/32 NO scanning from EDU 57 | ################ 58 | 59 | #redef Scan::whitelist_ip_file = "/YURT/feeds/BRO-feeds/ip-whitelist.scan" ; 60 | #redef Scan::whitelist_subnet_file = "/YURT/feeds/BRO-feeds/subnet-whitelist.scan" ; 61 | 62 | ## KnockKnockScan whitelist file 63 | # File to whitelist known hosts and services in order to prevent 64 | # FP due to sticky configurations of hosts/Laptops which move around 65 | # Example header and 1st row : 66 | # #fields exclude_ip exclude_port t comment 67 | # 11.3.2.5 123 tcp example comment 68 | 69 | redef ipportexclude_file = "/YURT/feeds/BRO-feeds/knockknock.exceptions"; 70 | 71 | ################ 72 | ### scan-summary.bro config 73 | # Scan-summary: if T will enable generation of scan-summary.log which tracks 74 | # start_time, end_time, detect_time of a scan 75 | # duration of scan 76 | # host many total connections scanner made 77 | # how many total uniq hosts did the scanner attempted/connected to 78 | # GeoIP location of the scanner 79 | # what Heuristic caught this scanner 80 | # detection latency 81 | # This is a slightly expensive to run policy in terms of increase in worker2manager events 82 | ################ 83 | 84 | redef enable_scan_summary = T; 85 | 86 | ################ 87 | ## KnockKnockScan specific configurations 88 | # These are KnockKnockScan Specific tweaks only. These don't affect any other heuristics 89 | # sensitive and sticky config ports 90 | ################ 91 | 92 | redef Scan::knock_medium_threshold_ports += { 93 | 17500/tcp, # dropbox-lan-sync 94 | 135/tcp, 95 | 139/tcp, 96 | 445/tcp, 97 | 0/tcp, 98 | 389/tcp, 99 | 88/tcp, 100 | 3268/tcp, 101 | 9200/tcp, 102 | }; 103 | 104 | redef Scan::knock_high_threshold_ports += { 105 | 53/tcp, 106 | 861/tcp, 107 | 80/tcp, 108 | 443/tcp, 109 | 8080/tcp, 110 | 113/tcp, 111 | 636/tcp, 112 | 135/tcp, 113 | 139/tcp, 114 | 17500/tcp, 115 | 18457/tcp, 116 | 3268/tcp, 117 | 3389/tcp, 118 | 3832/tcp, 119 | 389/tcp, 120 | 4242/tcp, 121 | 445/tcp, 122 | 52311/tcp, 123 | 5900/tcp, 124 | 60244/tcp, 125 | 60697/tcp, 126 | 7000/tcp, 127 | 7680/tcp, 128 | 8192/tcp, 129 | 8194/tcp, 130 | 8443/tcp, 131 | 88/tcp, 132 | 9001/tcp, 133 | 1095/tcp, 134 | 1094/tcp, 135 | }; 136 | 137 | ################ 138 | ## AddressScan 139 | ################ 140 | 141 | redef Scan::shut_down_thresh = 100; 142 | redef Scan::suppress_UDP_scan_checks = T; 143 | 144 | ################ 145 | ### These affect the entire scan detection ###### 146 | ################ 147 | 148 | # skip 149 | 150 | redef Scan::portexclude_file = "/YURT/feeds/BRO-feeds/scan-portexclude"; 151 | 152 | redef skip_outbound_services += { 153 | #22/tcp, 154 | 3128/tcp, 155 | 80/tcp, 156 | 8080/tcp, 157 | }; 158 | 159 | redef skip_scan_sources += { 160 | 255.255.255.255, # who knows why we see these, but we do 161 | }; 162 | 163 | redef skip_scan_nets += { }; 164 | 165 | # List of well known local server/ports to exclude for scanning 166 | # purposes. 167 | 168 | redef skip_dest_server_ports += { }; 169 | 170 | redef Scan::skip_services -= { 171 | 1/tcp, 172 | 11/tcp, 173 | 15/tcp, 174 | 19/tcp, 175 | 25/tcp, 176 | 42/tcp, 177 | 53/tcp, 178 | 80/tcp, 179 | 87/tcp, 180 | 109/tcp, 181 | 110/tcp, 182 | 111/tcp, 183 | 135/tcp, 184 | 137/tcp, 185 | 138/tcp, 186 | 139/tcp, 187 | 143/tcp, 188 | 407/tcp, 189 | 443/tcp, 190 | 445/tcp, 191 | 513/tcp, 192 | 514/tcp, 193 | 520/tcp, 194 | 540/tcp, 195 | 631/tcp, 196 | 8194/tcp, 197 | }; 198 | 199 | #redef Scan::skip_services += { 2323/tcp, 23/tcp, 445/tcp}; 200 | #redef Scan::skip_services += { 123/tcp, } ; 201 | #redef Scan::skip_services += { 111/tcp, } ; 202 | #redef Scan::skip_services += { 7547/tcp, 5555/tcp } ; 203 | 204 | # Dont flag internal hosts hitting external IPs on following ports 205 | # affects entire Scan Detection 206 | 207 | redef Scan::skip_outbound_services += { 208 | #22/tcp, 209 | 3128/tcp, 210 | 80/tcp, 211 | 8080/tcp, 212 | }; 213 | redef Scan::skip_scan_sources += { 214 | 255.255.255.255, # who knows why we see these, but we do 215 | } &redef; 216 | 217 | redef Scan::skip_scan_nets += { }; 218 | 219 | # List of well known local server/ports to exclude for scanning purposes. 220 | redef Scan::skip_dest_server_ports: set[addr, port] += { 221 | }; 222 | 223 | redef Scan::never_drop_nets += { 224 | Site::neighbor_nets 225 | }; 226 | redef can_drop_connectivity = T; 227 | redef dont_drop_locals = T &redef; 228 | 229 | event zeek_done() 230 | { 231 | #print fmt ("high thresh ports: %s", Scan::knock_high_threshold_ports); 232 | #print fmt ("medium thresh ports: %s", Scan::knock_medium_threshold_ports); 233 | } 234 | -------------------------------------------------------------------------------- /scripts/scan-inputs.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | #redef exit_only_after_terminate = T ; 4 | 5 | export { 6 | global PURGE_ON_WHITELIST = T; 7 | 8 | redef enum Notice::Type += { 9 | PurgeOnWhitelist, 10 | WhitelistAdd, 11 | WhitelistRemoved, 12 | WhitelistChanged, 13 | }; 14 | 15 | const read_whitelist_timer: interval = 10 secs; 16 | const update_whitelist_timer: interval = 5 mins; 17 | 18 | const read_files: set[string] = { } &redef; 19 | 20 | global whitelist_ip_file: string = "/YURT/feeds/BRO-feeds/ip-whitelist.scan.2" &redef; 21 | global whitelist_subnet_file: string = 22 | "/YURT/feeds/BRO-feeds/subnet-whitelist.scan.2" &redef; 23 | global blacklist_feeds: string = "/YURT/feeds/BRO-feeds/blacklist.scan" &redef; 24 | 25 | redef enum Notice::Type += { 26 | Whitelist, 27 | Blacklist, 28 | }; 29 | 30 | type wl_ip_Idx: record { 31 | ip: addr; 32 | }; 33 | 34 | type wl_ip_Val: record { 35 | ip: addr; 36 | comment: string &optional; 37 | }; 38 | 39 | type wl_subnet_Idx: record { 40 | nets: subnet; 41 | }; 42 | 43 | type wl_subnet_Val: record { 44 | nets: subnet; 45 | comment: string &optional; 46 | }; 47 | 48 | global whitelist_ip_table: table[addr] of wl_ip_Val = table(); 49 | global whitelist_subnet_table: table[subnet] of wl_subnet_Val = table(); 50 | 51 | type lineVals: record { 52 | d: string; 53 | }; 54 | 55 | const splitter: pattern = /\t/; 56 | 57 | global Scan::m_w_add_ip: event(ip: addr, comment: string); 58 | global Scan::m_w_update_ip: event(ip: addr, comment: string); 59 | global Scan::m_w_remove_ip: event(ip: addr, comment: string); 60 | 61 | global Scan::m_w_add_subnet: event(nets: subnet, comment: string); 62 | global Scan::m_w_update_subnet: event(nets: subnet, comment: string); 63 | global Scan::m_w_remove_subnet: event(nets: subnet, comment: string); 64 | } 65 | 66 | event reporter_error(t: time, msg: string, location: string) 67 | { 68 | if ( /whitelist.scan/ in msg ) { 69 | print fmt("bakwas error: %s, %s, %s", t, msg, location); 70 | # generate a notice 71 | } 72 | } 73 | 74 | event read_whitelist_ip(description: Input::TableDescription, tpe: Input::Event, 75 | left: wl_ip_Idx, right: wl_ip_Val) 76 | { 77 | local _msg = ""; 78 | local ip = right$ip; 79 | local comment = right$comment; 80 | local wl: wl_ip_Val; 81 | 82 | if ( tpe == Input::EVENT_NEW ) { 83 | #log_reporter(fmt (" scan-inputs.bro : NEW IP %s", ip), 0); 84 | 85 | whitelist_ip_table[ip] = wl; 86 | 87 | whitelist_ip_table[ip]$ip = ip; 88 | whitelist_ip_table[ip]$comment = comment; 89 | 90 | _msg = fmt("%s: %s", ip, comment); 91 | NOTICE([$note=WhitelistAdd, $src=ip, $msg=fmt("%s", _msg)]); 92 | 93 | if ( PURGE_ON_WHITELIST && Scan::is_catch_release_active(ip) ) { 94 | _msg = fmt("%s is removed from known_scanners after whitelist: %s", ip, 95 | known_scanners[ip]); 96 | delete known_scanners[ip]; 97 | 98 | @ifdef ( NetControl::unblock_address_catch_release ) 99 | if ( NetControl::unblock_address_catch_release(ip, _msg) ) { 100 | NOTICE([$note=PurgeOnWhitelist, $src=ip, $msg=fmt("%s", _msg)]); 101 | } 102 | @endif 103 | } 104 | 105 | @if ( Cluster::is_enabled() ) 106 | Broker::publish(Cluster::proxy_topic, Scan::m_w_add_ip, ip, comment); 107 | #event Scan::m_w_add_ip(ip, comment) ; 108 | @endif 109 | } 110 | 111 | if ( tpe == Input::EVENT_CHANGED ) { 112 | #log_reporter(fmt (" scan-inputs.bro : CHANGED IP %s, %s", ip, comment), 0); 113 | 114 | whitelist_ip_table[ip]$comment = comment; 115 | 116 | _msg = fmt("%s: %s", ip, comment); 117 | NOTICE([$note=WhitelistChanged, $src=ip, $msg=fmt("%s", _msg)]); 118 | 119 | @if ( Cluster::is_enabled() ) 120 | #event Scan::m_w_update_ip(ip, comment) ; 121 | Broker::publish(Cluster::proxy_topic, Scan::m_w_update_ip, ip, comment); 122 | @endif 123 | } 124 | 125 | if ( tpe == Input::EVENT_REMOVED ) { 126 | #log_reporter(fmt (" scan-inputs.bro : REMOVED IP %s", ip), 0); 127 | 128 | delete whitelist_ip_table[ip]; 129 | 130 | _msg = fmt("%s: %s", ip, comment); 131 | NOTICE([$note=WhitelistRemoved, $src=ip, $msg=fmt("%s", _msg)]); 132 | 133 | @if ( Cluster::is_enabled() ) 134 | #event Scan::m_w_remove_ip(ip, comment) ; 135 | Broker::publish(Cluster::proxy_topic, Scan::m_w_remove_ip, ip, comment); 136 | @endif 137 | } 138 | 139 | if ( ip !in whitelist_ip_table ) { 140 | whitelist_ip_table[ip] = wl; 141 | } 142 | 143 | whitelist_ip_table[ip]$ip = ip; 144 | whitelist_ip_table[ip]$comment = comment; 145 | } 146 | 147 | event read_whitelist_subnet(description: Input::TableDescription, 148 | tpe: Input::Event, left: wl_subnet_Idx, right: wl_subnet_Val) 149 | { 150 | local nets = right$nets; 151 | local comment = right$comment; 152 | local _msg = ""; 153 | 154 | #log_reporter(fmt (" SUBNETS: scan-inputs.bro : type %s", tpe), 0); 155 | 156 | if ( tpe == Input::EVENT_NEW ) { 157 | #log_reporter(fmt (" scan-inputs.bro : NEW Subnet %s", nets), 0); 158 | 159 | if ( nets !in whitelist_subnet_table ) { 160 | local wl: wl_subnet_Val; 161 | whitelist_subnet_table[nets] = wl; 162 | } 163 | 164 | whitelist_subnet_table[nets]$nets = nets; 165 | whitelist_subnet_table[nets]$comment = comment; 166 | 167 | _msg = fmt("%s: %s", nets, comment); 168 | NOTICE([$note=WhitelistAdd, $msg=fmt("%s", _msg)]); 169 | 170 | @if ( Cluster::is_enabled() ) 171 | #event Scan::m_w_add_subnet(nets, comment); 172 | Broker::publish(Cluster::proxy_topic, Scan::m_w_add_subnet, nets, comment); 173 | @endif 174 | } 175 | 176 | if ( tpe == Input::EVENT_CHANGED ) { 177 | #log_reporter(fmt (" scan-inputs.bro : CHANGED Subnet %s, %s", nets, comment), 0); 178 | whitelist_subnet_table[nets]$comment = comment; 179 | 180 | _msg = fmt("%s: %s", nets, comment); 181 | NOTICE([$note=WhitelistChanged, $msg=fmt("%s", _msg)]); 182 | 183 | @if ( Cluster::is_enabled() ) 184 | #event Scan::m_w_update_subnet(nets, comment); 185 | Broker::publish(Cluster::proxy_topic, Scan::m_w_update_subnet, nets, comment); 186 | @endif 187 | } 188 | 189 | if ( tpe == Input::EVENT_REMOVED ) { 190 | #log_reporter(fmt (" scan-inputs.bro : REMOVED Subnet %s", nets),0 ); 191 | delete whitelist_subnet_table[nets]; 192 | 193 | _msg = fmt("%s: %s", nets, comment); 194 | NOTICE([$note=WhitelistRemoved, $msg=fmt("%s", _msg)]); 195 | 196 | @if ( Cluster::is_enabled() ) 197 | #event Scan::m_w_remove_subnet(nets, comment) ; 198 | Broker::publish(Cluster::proxy_topic, Scan::m_w_remove_subnet, nets, comment); 199 | @endif 200 | } 201 | } 202 | 203 | @if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) 204 | event Scan::m_w_add_ip(ip: addr, comment: string) 205 | { 206 | local _msg = ""; 207 | #log_reporter(fmt ("scan-inputs.bro: m_w_add_ip: %s, %s", ip, comment), 0); 208 | 209 | if ( ip !in whitelist_ip_table ) { 210 | local wl: wl_ip_Val; 211 | whitelist_ip_table[ip] = wl; 212 | } 213 | 214 | whitelist_ip_table[ip]$ip = ip; 215 | whitelist_ip_table[ip]$comment = comment; 216 | 217 | # disable for the time-being to keep consistency with changed, removed 218 | # and webspiders are being logged already 219 | 220 | _msg = fmt("removing from known_scanners table due to whitelist: %s: %s", ip, comment); 221 | 222 | #NOTICE([$note=WhitelistAdd, $src=ip, $msg=fmt("%s", _msg)]); 223 | 224 | #if (PURGE_ON_WHITELIST && ip in known_scanners) 225 | if ( PURGE_ON_WHITELIST && is_catch_release_active(ip) ) { 226 | _msg = fmt("%s is removed from known_scanners after whitelist: %s", ip, 227 | known_scanners[ip]); 228 | delete known_scanners[ip]; 229 | 230 | @ifdef ( NetControl::unblock_address_catch_release ) 231 | if ( NetControl::unblock_address_catch_release(ip, _msg) ) { 232 | NOTICE([$note=PurgeOnWhitelist, $src=ip, $msg=fmt("%s", _msg)]); 233 | } 234 | @endif 235 | } 236 | } 237 | 238 | event Scan::m_w_update_ip(ip: addr, comment: string) 239 | { 240 | #log_reporter(fmt ("scan-inputs.bro: m_w_update_ip: %s, %s", ip, comment), 0); 241 | whitelist_ip_table[ip]$comment = comment; 242 | } 243 | 244 | event Scan::m_w_remove_ip(ip: addr, comment: string) 245 | { 246 | #log_reporter(fmt ("scan-inputs.bro: m_w_remove_ip: %s, %s", ip, comment), 0); 247 | delete whitelist_ip_table[ip]; 248 | } 249 | 250 | event Scan::m_w_add_subnet(nets: subnet, comment: string) 251 | { 252 | #log_reporter(fmt ("scan-inputs.bro: m_w_add_subnet: %s, %s", nets, comment), 0); 253 | if ( nets !in whitelist_subnet_table ) { 254 | local wl: wl_subnet_Val; 255 | whitelist_subnet_table[nets] = wl; 256 | } 257 | 258 | whitelist_subnet_table[nets]$nets = nets; 259 | whitelist_subnet_table[nets]$comment = comment; 260 | 261 | if ( PURGE_ON_WHITELIST ) { 262 | for ( ip in known_scanners ) { 263 | if ( ip in nets ) { 264 | local _msg = fmt("%s is removed from known_scanners after %s whitelist: %s", ip, nets, 265 | known_scanners[ip]); 266 | 267 | NOTICE([$note=PurgeOnWhitelist, $src=ip, $msg=fmt("%s", _msg)]); 268 | # ASH: FIXME : cannot delete inside a for loop 269 | # delete known_scanners[ip] ; 270 | 271 | @ifdef ( NetControl::unblock_address_catch_release ) 272 | NetControl::unblock_address_catch_release(ip, _msg); 273 | @endif 274 | } 275 | } 276 | } 277 | } 278 | 279 | event Scan::m_w_update_subnet(nets: subnet, comment: string) 280 | { 281 | #log_reporter(fmt ("scan-inputs.bro: m_w_update_subnet: %s, %s", nets, comment), 0); 282 | whitelist_subnet_table[nets]$comment = comment; 283 | } 284 | 285 | event Scan::m_w_remove_subnet(nets: subnet, comment: string) 286 | { 287 | #log_reporter(fmt ("scan-inputs.bro: m_w_remove_subnet: %s, %s", nets, comment), 0); 288 | delete whitelist_subnet_table[nets]; 289 | } 290 | @endif 291 | 292 | event update_whitelist() 293 | { 294 | #log_reporter(fmt ("%s running update_whitelist", network_time()), 0); 295 | #print fmt("%s", whitelist_ip_table); 296 | 297 | Input::force_update("whitelist_ip_scan"); 298 | Input::force_update("whitelist_subnet_scan"); 299 | 300 | schedule update_whitelist_timer { update_whitelist() }; 301 | } 302 | 303 | event read_whitelist() 304 | { 305 | if ( ! Cluster::is_enabled() || Cluster::local_node_type() == 306 | Cluster::MANAGER ) { 307 | Input::add_table([ 308 | $source=whitelist_ip_file, 309 | $name="whitelist_ip_scan", 310 | $idx=wl_ip_Idx, 311 | $val=wl_ip_Val, 312 | $destination=whitelist_ip_table, 313 | $mode=Input::REREAD, 314 | $ev=read_whitelist_ip]); 315 | 316 | Input::add_table([ 317 | $source=whitelist_subnet_file, 318 | $name="whitelist_subnet_scan", 319 | $idx=wl_subnet_Idx, 320 | $val=wl_subnet_Val, 321 | $destination=whitelist_subnet_table, 322 | $mode=Input::REREAD, 323 | $ev=read_whitelist_subnet]); 324 | } 325 | 326 | #schedule update_whitelist_timer { update_whitelist() } ; 327 | } 328 | 329 | event zeek_init() &priority=5 330 | { 331 | schedule read_whitelist_timer { read_whitelist() }; 332 | } 333 | 334 | event zeek_done() 335 | { #for ( ip in whitelist_ip_table) 336 | #{ 337 | # print fmt ("%s %s", ip , whitelist_ip_table[ip]); 338 | #} 339 | #for (nets in whitelist_subnet_table) 340 | #{ 341 | # print fmt ("%s %s", nets, whitelist_subnet_table[nets]); 342 | #} 343 | } 344 | -------------------------------------------------------------------------------- /scripts/scan-spikes.zeek: -------------------------------------------------------------------------------- 1 | # This policy tries to identify #of uniq scanners for a given port 2 | # and if #scanners for a port crosses a threshold, it generates a notice 3 | # basically, we want to find sudden spikes in scanning 4 | # esp with botnets such as mirai etc. 5 | 6 | module Scan; 7 | 8 | export { 9 | redef enum Notice::Type += { 10 | Spike, 11 | }; 12 | global uniq_scanners_on_port_threshold: vector of count = { 13 | 10, 14 | 20, 15 | 30, 16 | 50, 17 | 100, 18 | 250, 19 | 500, 20 | 1000, 21 | 2500, 22 | 5000, 23 | 10000, 24 | 25000, 25 | 50000, 26 | 100000, 27 | 150000, 28 | 200000, 29 | 300000, 30 | 5000000 31 | }; 32 | global check_scanners_threshold: function(v: vector of count, idx: table[port] of count, 33 | service: port, n: count): bool; 34 | 35 | global port_spike_idx: table[port] of count &default=0 &create_expire=7 days; 36 | global port_spikes: table[port] of set[addr] &create_expire=10 hrs; 37 | 38 | global Scan::track_port_spikes: event(service: port, scanner: addr); 39 | 40 | global Scan::PortSpike : event(service: port, scanner: addr); 41 | } 42 | 43 | event Scan::PortSpike (service: port, scanner: addr) 44 | { 45 | local u=fmt("%s",service); 46 | 47 | @if ( Cluster::is_enabled() ) 48 | Broker::publish(Cluster::manager_topic, Scan::track_port_spikes, service, scanner); 49 | @else 50 | event Scan::track_port_spikes(service, scanner); 51 | @endif 52 | } 53 | 54 | function check_scanners_threshold(v: vector of count, idx: table[port] of count, 55 | service: port, n: count): bool 56 | { 57 | if ( idx[service] < |v| && n >= v[idx[service]] ) { 58 | ++idx[service]; 59 | 60 | return ( T ); 61 | } else 62 | return ( F ); 63 | } 64 | 65 | event Scan::track_port_spikes(service: port, scanner: addr) 66 | { 67 | if ( service !in port_spikes ) { 68 | local a: set[addr]; 69 | port_spikes[service] = a; 70 | } 71 | 72 | if ( scanner !in port_spikes[service] ) 73 | add port_spikes[service][scanner]; 74 | 75 | local n = |port_spikes[service]|; 76 | 77 | local t = check_scanners_threshold(uniq_scanners_on_port_threshold, 78 | port_spike_idx, service, n); 79 | if ( t ) { 80 | local _msg = fmt("Spike on scanning of port %s with %s IPs", service, 81 | |port_spikes[service]|); 82 | NOTICE([$note=Spike, $src=scanner, $n=n, $p=service, $msg=fmt("%s", _msg)]); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /scripts/site-subnets.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | #redef exit_only_after_terminate = T ; 4 | export { 5 | redef enum Notice::Type += { 6 | # Indicates that an MD5 sum was calculated for an HTTP response body. 7 | Watched_Subnet, 8 | AllocatedSubnetRemoved, 9 | DarknetSubnetAdded, 10 | SubnetTableZero, 11 | ReactivatingSubnetTable, 12 | MissingSubnetsFeed, 13 | }; 14 | 15 | type subnet_Idx: record { 16 | Network: subnet; 17 | }; 18 | 19 | type subnet_Val: record { 20 | Network: subnet; 21 | Gateway: addr &optional; 22 | Enclaves: string &optional; 23 | Use: string &optional; 24 | }; 25 | 26 | global subnet_table: table[subnet] of subnet_Val = table() &redef; 27 | global subnet_feed = "/YURT/feeds/BRO-feeds/LBL-subnets.csv-LATEST_BRO" &redef; 28 | 29 | # flag to make sure LandMine doesn't activate until 30 | # subnet_feed is fully read - 2021-02-25 ash 31 | 32 | global SubnetCountToActivteLandMine = 65536 &redef; 33 | 34 | # a cache to keep memory of allocated subnets removed 35 | # from subnets.csv file for another hour before calling 36 | # that subnet a DarkNet 37 | 38 | global WATCH_REMOVED_ALLOCATED_SUBNET = 2 hrs &redef; 39 | 40 | global expire_allocated_cache: function(t: set[subnet], idx: subnet): interval; 41 | #global allocated_cache: table[subnet] of subnet_val = table() 42 | global allocated_cache: set[subnet] 43 | &create_expire=WATCH_REMOVED_ALLOCATED_SUBNET 44 | &expire_func=expire_allocated_cache; 45 | } 46 | 47 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) || ( ! Cluster::is_enabled() ) ) 48 | 49 | hook Notice::policy(n: Notice::Info) 50 | { 51 | if ( n$note == Scan::DarknetSubnetAdded ) { 52 | add n$actions[Notice::ACTION_LOG]; 53 | } 54 | if ( n$note == Scan::AllocatedSubnetRemoved ) { 55 | add n$actions[Notice::ACTION_LOG]; 56 | } 57 | if ( n$note == Scan::SubnetTableZero ) { 58 | add n$actions[Notice::ACTION_LOG]; 59 | } 60 | if ( n$note == Scan::ReactivatingSubnetTable ) { 61 | add n$actions[Notice::ACTION_LOG]; 62 | } 63 | if ( n$note == Scan::MissingSubnetsFeed ) { 64 | add n$actions[Notice::ACTION_LOG]; 65 | } 66 | } 67 | 68 | @endif 69 | 70 | function expire_allocated_cache(t: set[subnet], idx: subnet): interval 71 | { 72 | local _msg = fmt("Expiring the WATCH on REMOVED_ALLOCATED_SUBNET %s ", idx); 73 | 74 | NOTICE([$note=DarknetSubnetAdded, $msg=fmt("%s", _msg)]); 75 | 76 | return 0 secs; 77 | } 78 | 79 | # FAILURE-CHECK 80 | # we catch the error in subnet feed is empty and populate subnet_table with local_nets 81 | # so that LandMine detection doesn't block accidently 82 | 83 | event reporter_warning(t: time, msg: string, location: string) 84 | { 85 | local file_pat = fmt("%s\/Input::READER_ASCII", subnet_feed); 86 | 87 | if ( file_pat in msg ) { 88 | NOTICE([$note=MissingSubnetsFeed, $msg=fmt("%s", msg)]); 89 | # FIXME 90 | #if (subnet_feed in msg) 91 | #{ 92 | #for (nets in Scan::local_nets) 93 | # { 94 | # #print fmt("nets: %s", nets); 95 | # local sv: subnet_Val = [$Network=nets, $Gateway=0.0.0.0, $Enclaves="Site", $Use="Filling the empty subnet table"]; 96 | # Scan::subnet_table[nets] = sv ; 97 | #} 98 | #} 99 | } 100 | } 101 | 102 | event read_subnet_feed(description: Input::TableDescription, tpe: Input::Event, 103 | left: subnet_Idx, right: subnet_Val) 104 | { 105 | if ( tpe == Input::EVENT_NEW || tpe == Input::EVENT_CHANGED ) { 106 | if ( left$Network in allocated_cache ) 107 | delete allocated_cache[left$Network]; 108 | } 109 | 110 | if ( tpe == Input::EVENT_REMOVED ) { 111 | # we will still consider the removed subnet 112 | # as allocated for WATCH_REMOVED_ALLOCATED_SUBNET time 113 | 114 | add allocated_cache[right$Network]; 115 | 116 | local _msg = fmt("Subnet is removed from the list of allocated subnets: %s -> %s", left, right); 117 | NOTICE([ 118 | $note=AllocatedSubnetRemoved, 119 | $msg=fmt("%s", _msg), 120 | $identifier=cat("AllocatedSubnetRemoved"), 121 | $suppress_for=6 hrs]); 122 | } 123 | } 124 | 125 | event Input::end_of_data(name: string, source: string) 126 | { 127 | local _msg = ""; 128 | 129 | if ( source != subnet_feed ) 130 | return; 131 | 132 | print fmt("digested %s records in subnet_table", |Scan::subnet_table|); 133 | if ( subnet_feed in source ) { 134 | #print fmt ("1. name is %s source is %s", name, source); 135 | #print fmt("2. digested %s records in %s",|Scan::subnet_table|, source); 136 | 137 | # since subnet table is zero size 138 | # we poulate with local_nets 139 | 140 | if ( |Scan::subnet_table| == 0 ) { 141 | _msg = fmt("Looks like subnet Table is %s", |Scan::subnet_table|); 142 | 143 | # FIXME 144 | # for (nets in Scan::local_nets) 145 | # { #local sv: subnet_Val ; 146 | # #subnet_table[nets] = sv ; 147 | # subnet_table[nets] = [$Network=nets, $Gateway=0.0.0.0, $Enclaves="Site", $Use="Filling the empty subnet table"]; 148 | # } 149 | 150 | _msg += fmt(" expanded subnet table to local_nets: %s", subnet_table); 151 | 152 | NOTICE([$note=SubnetTableZero, $msg=fmt("%s", _msg)]); 153 | } else 154 | SubnetCountToActivteLandMine = |Scan::subnet_table|; 155 | 156 | if ( |Scan::subnet_table| > |Site::local_nets| ) { 157 | # FIXME 158 | #for (nets in Scan::local_nets) 159 | #{ 160 | # if (nets in subnet_table && /Filling/ in subnet_table[nets]$Use) 161 | # { 162 | # delete subnet_table[nets] ; 163 | # } 164 | # 165 | # 166 | #} 167 | 168 | _msg = fmt("Repopulated subnet table with %s Entries, removing local_nets automatically to activate LandMine again", 169 | SubnetCountToActivteLandMine); 170 | #NOTICE([$note=ReactivatingSubnetTable, $msg=fmt("%s", _msg)]); 171 | } 172 | } 173 | } 174 | 175 | event zeek_init() &priority=10 176 | { 177 | Input::add_table([ 178 | $source=subnet_feed, 179 | $name="scan_subnet_table", 180 | $idx=subnet_Idx, 181 | $val=subnet_Val, 182 | $destination=subnet_table, 183 | $mode=Input::REREAD, 184 | $ev=read_subnet_feed]); 185 | 186 | # Input::add_table([$source=subnet_feed, $name="subnet_table", $idx=subnet_Idx, $val=subnet_Val, $destination=subnet_table, $mode=Input::REREAD]); 187 | # $pred(typ: Input::Event, left: subnet_Idx, right: subnet_Val = { left$epo = to_lower(left$epo); return T;) }]); 188 | } 189 | 190 | event zeek_done() 191 | { 192 | #print fmt("bro-done subnet-bro"); 193 | #print fmt("digested %s records in subnet_table", |Scan::subnet_table|); 194 | #print fmt("subnet_table %s", Scan::subnet_table); 195 | } 196 | -------------------------------------------------------------------------------- /scripts/skip-services.zeek: -------------------------------------------------------------------------------- 1 | #redef exit_only_after_terminate=T ; 2 | 3 | module Scan; 4 | 5 | export { 6 | redef enum Notice::Type += { 7 | SkipPort, 8 | RemoveSkipPort, 9 | }; 10 | 11 | global portexclude_file = "" &redef; 12 | redef portexclude_file = "/YURT/feeds/BRO-feeds/scan-portexclude"; 13 | 14 | type Idx: record { 15 | skip_port: port &type_column="t"; 16 | }; 17 | 18 | type portexclude_Val: record { 19 | skip_port: port &type_column="t"; 20 | comment: string &optional; 21 | }; 22 | 23 | global port_exclude_table: table[port] of portexclude_Val = table(); 24 | } 25 | 26 | event port_exclude(description: Input::TableDescription, tpe: Input::Event, 27 | left: Idx, right: portexclude_Val) 28 | { 29 | local _msg = ""; 30 | 31 | if ( tpe == Input::EVENT_NEW ) { 32 | _msg = fmt("Port %s added to skip_services", left$skip_port); 33 | add skip_services[left$skip_port]; 34 | NOTICE([$note=SkipPort, $msg=fmt("%s", _msg)]); 35 | } 36 | 37 | if ( tpe == Input::EVENT_REMOVED ) { 38 | _msg = fmt("Port %s removed from skip_services", left$skip_port); 39 | delete skip_services[left$skip_port]; 40 | NOTICE([$note=RemoveSkipPort, $msg=fmt("%s", _msg)]); 41 | } 42 | } 43 | 44 | event zeek_init() 45 | { 46 | Input::add_table([ 47 | $source=portexclude_file, 48 | $mode=Input::REREAD, 49 | $name="port_exclude", 50 | $destination=port_exclude_table, 51 | $idx=Idx, 52 | $val=portexclude_Val, 53 | $ev=port_exclude]); 54 | } 55 | -------------------------------------------------------------------------------- /scripts/ss.zeek: -------------------------------------------------------------------------------- 1 | # code for scan-summary 2 | # aashish 3 | 4 | module Scan; 5 | 6 | export { 7 | global scan_candidates: set[addr] &create_expire=1 day; 8 | 9 | # scan_summary module 10 | # authoritative table which keeps track of scanner 11 | # status and statistics 12 | 13 | global LOGGING_TIME = 60 mins; # 60 mins ; 14 | global _max_expire_time = 4 hrs; # 1 day ; 15 | global _worker_time = 10 mins; # 1 day ; 16 | 17 | type log_state: enum { 18 | DETECT, 19 | ONGOING, 20 | EXPIRE, 21 | UPDATE, 22 | FINISH, 23 | SUMMARY 24 | }; 25 | type scan_stats: record { 26 | scanner: addr &log; 27 | state: log_state &default=DETECT; 28 | #status: bool &default=F ; 29 | #sport: port &log &optional ; 30 | detection: string &log &optional &default=""; 31 | start_ts: time &log &optional &default=double_to_time(0.0); 32 | end_ts: time &log &optional &default=double_to_time(0.0); 33 | detect_ts: time &log &optional &default=double_to_time(0.0); 34 | total_conn: count &default=0 &log; 35 | #hosts: set[addr] &log &optional ; 36 | hosts: opaque of cardinality &default=hll_cardinality_init(0.1, 0.999); 37 | #detect_count: count &log &optional &default=0; 38 | event_peer: string &log &optional; 39 | }; 40 | 41 | global expire_worker_stats: function(t: table[addr] of scan_stats, idx: addr) 42 | : interval; 43 | global worker_stats: table[addr] of scan_stats = table() 44 | &create_expire=_worker_time &expire_func=expire_worker_stats; 45 | 46 | global report_manager_stats: function(t: table[addr] of scan_stats, idx: addr) 47 | : interval; 48 | global manager_stats: table[addr] of scan_stats = table() &create_expire=20 secs 49 | &expire_func=report_manager_stats; 50 | 51 | #setting up logging for scan_summary.log 52 | redef enum Log::ID += { 53 | summary_LOG 54 | }; 55 | 56 | type scan_stats_log: record { 57 | ts: time &default=network_time() &log; 58 | scanner: addr &log; 59 | state: log_state &log &default=DETECT; 60 | detection: string &log &optional &default=""; 61 | start_ts: time &log &optional &default=double_to_time(0.0); 62 | end_ts: time &log &optional &default=double_to_time(0.0); 63 | detect_ts: time &log &optional &default=double_to_time(0.0); 64 | detect_latency: interval &log &optional; 65 | total_conn: count &default=0 &log &optional; 66 | total_hosts_scanned: count &default=0 &log &optional; 67 | 68 | # computed value no need to store 69 | duration: interval &log &optional; 70 | scan_rate: double &log &optional; 71 | country_code: string &log &optional; 72 | region: string &log &optional; 73 | city: string &log &optional; 74 | #geoip_info: geo_location &log &optional; 75 | distance: double &log &optional; 76 | event_peer: string &log &optional; 77 | }; 78 | 79 | global log_scan_summary: function(ss: scan_stats, state: log_state); 80 | global aggregate_scan_stats: event(ss: scan_stats); 81 | 82 | # Used for distance calculations 83 | global local_ip: addr = 128.3.0.0 &redef; 84 | } 85 | 86 | event zeek_init() 87 | { 88 | when ( local myaddrs = lookup_hostname(gethostname()) ) { 89 | for ( ip in myaddrs ) { 90 | if ( is_v4_addr(ip) ) { 91 | Scan::local_ip = ip; 92 | local myloc = lookup_location(Scan::local_ip); 93 | if ( myloc?$city ) { 94 | print fmt("Using %s, %s as local location", myloc$city, myloc$region); 95 | } 96 | break; 97 | } 98 | } 99 | } 100 | } 101 | 102 | event zeek_init() &priority=5 103 | { 104 | Log::create_stream(Scan::summary_LOG, [$columns=scan_stats_log]); 105 | } 106 | 107 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::WORKER ) || ( ! Cluster::is_enabled() ) ) 108 | function Scan::expire_worker_stats(t: table[addr] of scan_stats, idx: addr) 109 | : interval 110 | { 111 | if ( idx in known_scanners ) { 112 | #log_reporter(fmt("expire_worker_stats: sending to manager: %s", t[idx]),10); 113 | @if ( Cluster::is_enabled() ) 114 | local scan_sub=get_subnet(idx); 115 | Cluster::publish_hrw(Cluster::proxy_pool, scan_sub, Scan::aggregate_scan_stats, t[idx]); 116 | @else 117 | event Scan::aggregate_scan_stats(t[idx]); 118 | @endif 119 | } else if ( idx in scan_candidates && network_time() - t[idx]$start_ts < 120 | _max_expire_time ) { 121 | #we just hold scan_candidate start time more in memory 122 | # until its flagged as scanner - >= 1 conn/ day sensitivity 123 | 124 | #log_reporter(fmt("expire_worker_stats: extended timer: %s", t[idx]),10); 125 | return _worker_time; 126 | } 127 | 128 | #log_reporter(fmt("expire_worker_stats: 0 secs: %s", t[idx]),10); 129 | return 0 secs; 130 | } 131 | @endif 132 | 133 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::WORKER ) || ( ! Cluster::is_enabled() ) ) 134 | event connection_state_remove(c: connection) 135 | { 136 | local orig = c$id$orig_h; 137 | local resp = c$id$resp_h; 138 | local orig_p = c$id$orig_p; 139 | 140 | if ( orig in Site::local_nets ) 141 | return; 142 | 143 | #if (orig !in scan_candidates) 144 | # return ; 145 | 146 | if ( orig !in worker_stats ) { 147 | local ss: scan_stats; 148 | worker_stats[orig] = ss; 149 | worker_stats[orig]$start_ts = c$start_time; 150 | worker_stats[orig]$event_peer = fmt("%s", Cluster::node); 151 | #worker_stats[orig]$hosts = set() ; 152 | } 153 | 154 | worker_stats[orig]$scanner = orig; 155 | worker_stats[orig]$end_ts = c$start_time; 156 | worker_stats[orig]$total_conn += 1; 157 | hll_cardinality_add(worker_stats[orig]$hosts, resp); 158 | #add worker_stats[orig]$hosts [resp] ; 159 | } 160 | @endif 161 | 162 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::PROXY ) || ( ! Cluster::is_enabled() ) ) 163 | function Scan::report_manager_stats(t: table[addr] of scan_stats, idx: addr) 164 | : interval 165 | { 166 | #log_reporter (fmt ("Running report_manager_stats: %s, size: %s", t[idx], |manager_stats|),10); 167 | if ( idx in known_scanners ) { 168 | if ( t[idx]$state == DETECT ) { 169 | log_scan_summary(t[idx], DETECT); 170 | t[idx]$state = UPDATE; 171 | } else { 172 | log_scan_summary(t[idx], UPDATE); 173 | } 174 | } 175 | 176 | # we need to expire slow scanners so either if end_ts is older than 1 day 177 | if ( network_time() - t[idx]$end_ts > _max_expire_time ) { 178 | #log_reporter (fmt ("report_manager_stats: expiring: %s, size: %s", t[idx], |manager_stats|),10); 179 | log_scan_summary(t[idx], FINISH); 180 | return 0 secs; 181 | } 182 | 183 | return LOGGING_TIME; 184 | } 185 | 186 | event Scan::aggregate_scan_stats(ss: scan_stats) 187 | { 188 | #log_reporter(fmt ("inside aggregate_scan_stats %s", ss),10); 189 | 190 | local orig = ss$scanner; 191 | 192 | if ( orig !in known_scanners ) 193 | return; 194 | 195 | if ( orig !in manager_stats ) { 196 | local s: scan_stats; 197 | manager_stats[orig] = s; 198 | manager_stats[orig]$scanner = orig; 199 | manager_stats[orig]$start_ts = ss$start_ts; 200 | manager_stats[orig]$end_ts = ss$end_ts; 201 | manager_stats[orig]$detect_ts = ss$end_ts; 202 | #manager_stats[orig]$hosts=set() ; 203 | } 204 | 205 | manager_stats[orig]$detection = known_scanners[orig]$detection; 206 | 207 | # update all the variables and aggregate based on what workers are returning 208 | 209 | local m_start_ts = manager_stats[orig]$start_ts; 210 | local m_end_ts = manager_stats[orig]$end_ts; 211 | 212 | manager_stats[orig]$start_ts = ss$start_ts < m_start_ts ? ss$start_ts : 213 | m_start_ts; 214 | manager_stats[orig]$end_ts = ss$end_ts > m_end_ts ? ss$end_ts : m_end_ts; 215 | manager_stats[orig]$total_conn += ss$total_conn; 216 | 217 | local peer = fmt("%s", Cluster::node); 218 | 219 | #manager_stats[orig]$event_peer = fmt ("%s", Cluster::node); 220 | manager_stats[orig]$event_peer = fmt("%s", peer_description); 221 | hll_cardinality_merge_into(manager_stats[orig]$hosts, ss$hosts); 222 | 223 | #log_reporter(fmt ("inside aggregate_scan_stats II %s, size manager_stats: %s", manager_stats[orig], |manager_stats|),10); 224 | } 225 | 226 | function log_scan_summary(ss: scan_stats, state: log_state) 227 | { 228 | #log_reporter(fmt("log_scan_summary: %s: state: %s", ss, state),5) ; 229 | 230 | local info: scan_stats_log; 231 | 232 | #log_reporter(fmt("LSS: log_scan_summary: KS: %s, scan_summary: %s", known_scanners[ss$scanner],ss),5) ; 233 | # ash local detect_ts = known_scanners[ss$scanner]$detect_ts ; 234 | 235 | # preserve detect_ts until scan_summary expires now 236 | if ( ss$scanner in manager_stats ) 237 | manager_stats[ss$scanner]$detect_ts = ss$detect_ts; 238 | else 239 | log_reporter(fmt("ss$scanner not found in scan_summary : %s, %s", ss$scanner, ss), 5); 240 | 241 | local scanner = ss$scanner; 242 | 243 | info$ts = network_time(); 244 | info$scanner = scanner; 245 | info$state = state; 246 | info$detection = ss$detection; 247 | info$start_ts = ss$start_ts; 248 | info$end_ts = ss$end_ts; 249 | info$detect_ts = ss$detect_ts; 250 | info$detect_latency = info$detect_ts - info$start_ts; 251 | info$total_conn = ss$total_conn; 252 | info$total_hosts_scanned = double_to_count(hll_cardinality_estimate(ss$hosts)); #|ss$hosts| ; 253 | #info$total_hosts_scanned = |ss$hosts|; 254 | info$duration = info$end_ts - info$start_ts; # ss$end_ts - ss$start_ts ; 255 | info$scan_rate = info$total_hosts_scanned == 0 ? 0 : interval_to_double( 256 | info$duration) / info$total_hosts_scanned; 257 | local geoip_info = lookup_location(ss$scanner); 258 | local peer = Cluster::node; 259 | info$event_peer = fmt("%s", peer_description); 260 | 261 | info$country_code = geoip_info?$country_code ? geoip_info$country_code : ""; 262 | info$region = geoip_info?$region ? geoip_info$region : ""; 263 | info$city = geoip_info?$city ? geoip_info$city : ""; 264 | 265 | info$distance = 0; 266 | info$distance = haversine_distance_ip(Scan::local_ip, ss$scanner); 267 | #info$event_peer = ss$event_peer ; 268 | 269 | #log_reporter(fmt("log_scan_summary: info is : %s", info),5) ; 270 | 271 | Log::write(Scan::summary_LOG, info); 272 | } 273 | 274 | @endif 275 | 276 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) || ( ! Cluster::is_enabled() ) ) 277 | event finish_scan_summary(idx: addr) 278 | { #log_reporter(fmt("finish_scan_summary: %s, %s", idx, manager_stats[idx]),10); 279 | #log_scan_summary(manager_stats[idx], FINISH); 280 | #manager_stats[idx]$state = FINISH; 281 | } 282 | @endif 283 | -------------------------------------------------------------------------------- /scripts/stats.zeek: -------------------------------------------------------------------------------- 1 | module Scan; 2 | 3 | export { 4 | global gather_statistics = T &redef; 5 | 6 | type scan_counters: record { 7 | new_conn_counter: count &log &default=0; 8 | is_catch_release_active: count &log &default=0; 9 | known_scanners_counter: count &log &default=0; 10 | not_scanner: count &log &default=0; 11 | darknet_counter: count &log &default=0; 12 | not_darknet_counter: count &log &default=0; 13 | already_scanner_counter: count &log &default=0; 14 | filteration_entry: count &log &default=0; 15 | filteration_success: count &log &default=0; 16 | 17 | c_knock_filterate: count &log &default=0; 18 | c_knock_checkscan: count &log &default=0; 19 | c_knock_core: count &log &default=0; 20 | 21 | c_land_filterate: count &log &default=0; 22 | c_land_checkscan: count &log &default=0; 23 | c_land_core: count &log &default=0; 24 | 25 | c_backscat_filterate: count &log &default=0; 26 | c_backscat_checkscan: count &log &default=0; 27 | c_backscat_core: count &log &default=0; 28 | 29 | c_addressscan_filterate: count &log &default=0; 30 | c_addressscan_checkscan: count &log &default=0; 31 | c_addressscan_core: count &log &default=0; 32 | 33 | check_scan_counter: count &log &default=0; 34 | 35 | worker_to_manager_counter: count &log &default=0; 36 | run_scan_detection: count &log &default=0; 37 | check_scan_cache: count &log &default=0; 38 | 39 | event_peer: string &log &optional; 40 | }; 41 | 42 | global worker_count = 0; 43 | 44 | global stat_freq = 1 hrs; 45 | global s_counters: scan_counters; 46 | 47 | global aggregate_workers: table[string] of bool &default=F; 48 | 49 | global Scan::m_w_send_performance_counters: event(send: bool); 50 | global Scan::w_m_update_performance_counters: event(sc: scan_counters); 51 | } 52 | 53 | function reset_counters() 54 | { 55 | s_counters$new_conn_counter = 0; 56 | s_counters$is_catch_release_active = 0; 57 | s_counters$known_scanners_counter = 0; 58 | s_counters$not_scanner = 0; 59 | s_counters$darknet_counter = 0; 60 | s_counters$not_darknet_counter = 0; 61 | s_counters$already_scanner_counter = 0; 62 | s_counters$filteration_entry = 0; 63 | s_counters$filteration_success = 0; 64 | 65 | s_counters$c_knock_filterate = 0; 66 | s_counters$c_land_filterate = 0; 67 | s_counters$c_backscat_filterate = 0; 68 | s_counters$c_addressscan_filterate = 0; 69 | 70 | s_counters$check_scan_counter = 0; 71 | s_counters$check_scan_cache = 0; 72 | 73 | # since these are manager counters we don't zero these 74 | # s_counters$worker_to_manager_counter = 0 ; 75 | # s_counters$run_scan_detection = 0 ; 76 | # s_counters$c_knock_checkscan = 0 ; 77 | # s_counters$c_knock_core = 0 ; 78 | # s_counters$c_land_checkscan = 0 ; 79 | # s_counters$c_land_core = 0 ; 80 | # s_counters$c_backscat_checkscan = 0 ; 81 | # s_counters$c_backscat_core = 0 ; 82 | # s_counters$c_addressscan_checkscan = 0 ; 83 | # s_counters$c_addressscan_core = 0 ; 84 | } 85 | 86 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) || ( ! Cluster::is_enabled() ) ) 87 | event dump_stats() 88 | { 89 | #log_reporter(fmt ("dump_stats calling send_perf_counters: %s", s_counters ),0); 90 | event Scan::m_w_send_performance_counters(T); 91 | 92 | schedule stat_freq { dump_stats() }; 93 | } 94 | 95 | event zeek_init() 96 | { 97 | if ( gather_statistics ) 98 | schedule stat_freq { dump_stats() }; 99 | } 100 | 101 | @endif 102 | 103 | #@if ( Cluster::is_enabled() ) 104 | #@load base/frameworks/cluster 105 | #redef Cluster::manager2worker_events += /Scan::m_w_send_performance_counters/ ; 106 | #redef Cluster::worker2manager_events += /Scan::w_m_update_performance_counters/ ; 107 | #@endif 108 | 109 | @if ( Cluster::is_enabled() ) 110 | 111 | @if ( Cluster::local_node_type() == Cluster::MANAGER ) 112 | event zeek_init() 113 | { 114 | Broker::auto_publish(Cluster::worker_topic, 115 | Scan::m_w_send_performance_counters); 116 | } 117 | @else 118 | event zeek_init() 119 | { 120 | Broker::auto_publish(Cluster::manager_topic, 121 | Scan::w_m_update_performance_counters); 122 | } 123 | @endif 124 | 125 | @endif 126 | 127 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) || ( ! Cluster::is_enabled() ) ) 128 | 129 | event Scan::m_w_send_performance_counters(send: bool) 130 | { 131 | worker_count = 0; 132 | #log_reporter(fmt ("m_w_send_performance_counters calling w_m_update_performance_counters" ),0); 133 | event Scan::w_m_update_performance_counters(s_counters); 134 | reset_counters(); 135 | } 136 | 137 | @endif 138 | 139 | @if ( ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) || ( ! Cluster::is_enabled() ) ) 140 | 141 | event Scan::w_m_update_performance_counters(sc: scan_counters) 142 | { 143 | #log_reporter(fmt ("inside w_m_update_performance_counters : %s", sc),0); 144 | # log_reporter(fmt("Got counters: %s", sc),2); 145 | 146 | # aggregate the numbers now 147 | s_counters$new_conn_counter += sc$new_conn_counter; 148 | s_counters$is_catch_release_active += sc$is_catch_release_active; 149 | s_counters$known_scanners_counter += sc$known_scanners_counter; 150 | s_counters$not_scanner += sc$not_scanner; 151 | s_counters$darknet_counter += sc$darknet_counter; 152 | s_counters$not_darknet_counter += sc$not_darknet_counter; 153 | s_counters$already_scanner_counter += sc$already_scanner_counter; 154 | s_counters$filteration_entry += sc$filteration_entry; 155 | s_counters$filteration_success += sc$filteration_success; 156 | s_counters$c_knock_filterate += sc$c_knock_filterate; 157 | s_counters$c_knock_checkscan += sc$c_knock_checkscan; 158 | s_counters$c_knock_core += sc$c_knock_core; 159 | s_counters$c_land_filterate += sc$c_land_filterate; 160 | s_counters$c_land_checkscan += sc$c_land_checkscan; 161 | s_counters$c_land_core += sc$c_land_core; 162 | s_counters$c_backscat_filterate += sc$c_backscat_filterate; 163 | s_counters$c_backscat_checkscan += sc$c_backscat_checkscan; 164 | s_counters$c_backscat_core += sc$c_backscat_core; 165 | s_counters$c_addressscan_filterate += sc$c_addressscan_filterate; 166 | s_counters$c_addressscan_checkscan += sc$c_addressscan_checkscan; 167 | s_counters$c_addressscan_core += sc$c_addressscan_core; 168 | s_counters$check_scan_counter += sc$check_scan_counter; 169 | s_counters$worker_to_manager_counter += sc$worker_to_manager_counter; 170 | s_counters$run_scan_detection += sc$run_scan_detection; 171 | s_counters$check_scan_cache += sc$check_scan_cache; 172 | 173 | local c_worker = sc?$event_peer ? sc$event_peer : ""; 174 | 175 | if ( c_worker !in aggregate_workers ) 176 | aggregate_workers[c_worker] = T; 177 | 178 | if ( |aggregate_workers| == Cluster::get_active_node_count(Cluster::WORKER) ) { 179 | log_reporter(fmt("STATISTICS: %s", s_counters), 0); 180 | 181 | # reset the worker reporting table again 182 | # FIXME: this 2 lines will crash 183 | #for (w in aggregate_workers) 184 | # delete aggregate_workers[w] ; 185 | 186 | aggregate_workers = table(); 187 | 188 | # since these are manager counters we don't zero these 189 | # s_counters$worker_to_manager_counter = 0 ; 190 | # s_counters$run_scan_detection = 0 ; 191 | # s_counters$c_knock_checkscan = 0 ; 192 | # s_counters$c_knock_core = 0 ; 193 | # s_counters$c_land_checkscan = 0 ; 194 | # s_counters$c_land_core = 0 ; 195 | # s_counters$c_backscat_checkscan = 0 ; 196 | # s_counters$c_backscat_core = 0 ; 197 | # s_counters$c_addressscan_checkscan = 0 ; 198 | # s_counters$c_addressscan_core = 0 ; 199 | 200 | # reset worker counts 201 | worker_count = 0; 202 | } 203 | 204 | # #log_reporter(fmt ("II + inside w_m_update_performance_counters : %s", sc),0); 205 | } 206 | 207 | @endif 208 | -------------------------------------------------------------------------------- /scripts/trw-impl.zeek: -------------------------------------------------------------------------------- 1 | # $Id: trw.bro 2911 2006-05-06 17:58:43Z vern $ 2 | 3 | module TRW; 4 | 5 | export { 6 | redef enum Notice::Type += { 7 | TRWAddressScan, # source flagged as scanner by TRW algorithm 8 | TRWScanSummary, # summary of scanning activities reported by TRW 9 | }; 10 | 11 | # Activate TRW if T. 12 | global use_TRW_algorithm = F &redef; 13 | 14 | # Tell TRW not to flag a friendly remote. 15 | global do_not_flag_friendly_remotes = T &redef; 16 | 17 | # Set of services for outbound connections that are possibly triggered 18 | # by incoming connections. 19 | const triggered_outbound_services = { 20 | 113/tcp, 21 | 79/tcp, 22 | 20/tcp, 23 | } &redef; 24 | 25 | # The following correspond to P_D and P_F in the TRW paper, i.e., the 26 | # desired detection and false positive probabilities. 27 | global target_detection_prob = 0.99 &redef; 28 | global target_false_positive_prob = 0.01 &redef; 29 | 30 | # Given a legitimate remote, the probability that its connection 31 | # attempt will succeed. 32 | global theta_zero = 0.8 &redef; 33 | 34 | # Given a scanner, the probability that its connection attempt 35 | # will succeed. 36 | global theta_one = 0.2 &redef; 37 | 38 | # These variables the user usually won't alter, except they 39 | # might want to adjust the expiration times, which is why 40 | # they're exported here. 41 | global scan_sources: set[addr] &write_expire=1 hr; 42 | global benign_sources: set[addr] &write_expire=1 hr; 43 | 44 | global failed_locals: set[addr, addr] &write_expire=30 mins; 45 | global successful_locals: set[addr, addr] &write_expire=30 mins; 46 | 47 | global lambda: table[addr] of double &default=1.0 &write_expire=30 mins; 48 | global num_scanned_locals: table[addr] of count &default=0 &write_expire=30 mins; 49 | 50 | # Function called to perform TRW analysis. 51 | global check_TRW_scan: function(c: connection, state: string, reverse: bool): bool; 52 | } 53 | 54 | # Set of remote hosts that have been successfully accessed by local hosts. 55 | global friendly_remotes: set[addr] &read_expire=30 mins; 56 | 57 | # Set of local honeypot hosts - for internal use at LBL. 58 | global honeypot: set[addr]; 59 | 60 | # Approximate solutions for upper and lower thresholds. 61 | global eta_zero: double; # initialized when Bro starts 62 | global eta_one: double; 63 | 64 | event zeek_init() 65 | { 66 | eta_zero = ( 1 - target_detection_prob ) / ( 1 - target_false_positive_prob ); 67 | eta_one = target_detection_prob / target_false_positive_prob; 68 | } 69 | 70 | event TRW_scan_summary(orig: addr) 71 | { 72 | NOTICE([ 73 | $note=TRWScanSummary, 74 | $src=orig, 75 | $msg=fmt("%s scanned a total of %d hosts", orig, num_scanned_locals[orig])]); 76 | } 77 | 78 | function check_TRW_scan(c: connection, state: string, reverse: bool): bool 79 | { 80 | local id = c$id; 81 | 82 | local service = "ftp-data" in c$service ? 20/tcp : ( reverse ? id$orig_p : 83 | id$resp_p ); 84 | local orig = reverse ? id$resp_h : id$orig_h; 85 | local resp = reverse ? id$orig_h : id$resp_h; 86 | local outbound = Site::is_local_addr(orig); 87 | 88 | # Mark a remote as friendly if it is successfully accessed by 89 | # a local with protocols other than triggered_outbound_services. 90 | # XXX There is an ambiguity to determine who initiated a 91 | # connection when the status is "OTH". 92 | if ( outbound ) { 93 | if ( resp !in scan_sources 94 | && service !in triggered_outbound_services 95 | && orig !in honeypot 96 | && state != "OTH" ) 97 | add friendly_remotes[resp]; 98 | 99 | return F; 100 | } 101 | 102 | if ( orig in scan_sources ) 103 | return T; 104 | 105 | if ( orig in benign_sources ) 106 | return F; 107 | 108 | if ( do_not_flag_friendly_remotes && orig in friendly_remotes ) 109 | return F; 110 | 111 | # Start TRW evaluation. 112 | local flag = +0; 113 | local resp_byte = reverse ? c$orig$size : c$resp$size; 114 | local established = T; 115 | 116 | if ( state == "S0" 117 | || state == "REJ" 118 | || state == "OTH" 119 | || ( state == "RSTOS0" && resp_byte <= 0 ) ) 120 | established = F; 121 | 122 | if ( ! established || resp in honeypot ) { 123 | if ( [orig, resp] !in failed_locals ) { 124 | flag = 1; 125 | add failed_locals[orig, resp]; 126 | } 127 | } 128 | else if ( [orig, resp] !in successful_locals ) { 129 | flag = -1; 130 | add successful_locals[orig, resp]; 131 | } 132 | 133 | if ( flag == 0 ) 134 | return F; 135 | 136 | local ratio = 1.0; 137 | 138 | # Update the corresponding likelihood ratio of orig. 139 | if ( theta_zero <= 0 140 | || theta_zero >= 1 141 | || theta_one <= 0 142 | || theta_one >= 1 143 | || theta_one >= theta_zero ) { 144 | # Error: theta_zero should be between 0 and 1. 145 | print fmt("bad theta_zero/theta_one in check_TRW_scan"); 146 | use_TRW_algorithm = F; 147 | return F; 148 | } 149 | 150 | if ( flag == 1 ) 151 | ratio = ( 1 - theta_one ) / ( 1 - theta_zero ); 152 | 153 | if ( flag == -1 ) 154 | ratio = theta_one / theta_zero; 155 | 156 | ++num_scanned_locals[orig]; 157 | 158 | lambda[orig] = lambda[orig] * ratio; 159 | local updated_lambda = lambda[orig]; 160 | 161 | if ( target_detection_prob <= 0 162 | || target_detection_prob >= 1 163 | || target_false_positive_prob <= 0 164 | || target_false_positive_prob >= 1 ) { 165 | # Error: target probabilities should be between 0 and 1 166 | print fmt("bad target probabilities in check_TRW_scan"); 167 | use_TRW_algorithm = F; 168 | return F; 169 | } 170 | 171 | if ( updated_lambda > eta_one ) { 172 | add scan_sources[orig]; 173 | NOTICE([ 174 | $note=TRWAddressScan, 175 | $src=orig, 176 | $msg=fmt("%s scanned a total of %d hosts", orig, num_scanned_locals[orig])]); 177 | schedule 1 day { TRW_scan_summary(orig) }; 178 | return T; 179 | } 180 | 181 | if ( updated_lambda < eta_zero ) 182 | add benign_sources[orig]; 183 | 184 | return F; 185 | } 186 | -------------------------------------------------------------------------------- /scripts/trw.zeek: -------------------------------------------------------------------------------- 1 | # $Id: trw.bro 3297 2006-06-18 00:56:58Z vern $ 2 | # 3 | # Load this file to actiate TRW analysis. 4 | 5 | @load ./trw-impl 6 | 7 | redef TRW::use_TRW_algorithm = T; 8 | 9 | function conn_state(c: connection, trans: transport_proto): string 10 | { 11 | local os = c$orig$state; 12 | local rs = c$resp$state; 13 | 14 | local o_inactive = os == TCP_INACTIVE || os == TCP_PARTIAL; 15 | local r_inactive = rs == TCP_INACTIVE || rs == TCP_PARTIAL; 16 | 17 | if ( trans == tcp ) { 18 | if ( rs == TCP_RESET ) { 19 | if ( os == TCP_SYN_SENT 20 | || os == TCP_SYN_ACK_SENT 21 | || ( os == TCP_RESET && c$orig$size == 0 && c$resp$size == 0 ) ) 22 | return "REJ"; 23 | else if ( o_inactive ) 24 | return "RSTRH"; 25 | else 26 | return "RSTR"; 27 | } else if ( os == TCP_RESET ) 28 | return r_inactive ? "RSTOS0" : "RSTO"; 29 | else if ( rs == TCP_CLOSED && os == TCP_CLOSED ) 30 | return "SF"; 31 | else if ( os == TCP_CLOSED ) 32 | return r_inactive ? "SH" : "S2"; 33 | else if ( rs == TCP_CLOSED ) 34 | return o_inactive ? "SHR" : "S3"; 35 | else if ( os == TCP_SYN_SENT && rs == TCP_INACTIVE ) 36 | return "S0"; 37 | else if ( os == TCP_ESTABLISHED && rs == TCP_ESTABLISHED ) 38 | return "S1"; 39 | else 40 | return "OTH"; 41 | } 42 | else if ( trans == udp ) { 43 | if ( os == UDP_ACTIVE ) 44 | return rs == UDP_ACTIVE ? "SF" : "S0"; 45 | else 46 | return rs == UDP_ACTIVE ? "SHR" : "OTH"; 47 | } 48 | else 49 | return "OTH"; 50 | } 51 | #event connection_established(c: connection) 52 | # { 53 | # local is_reverse_scan = (c$orig$state == TCP_INACTIVE); 54 | # local trans = get_port_transport_proto(c$id$orig_p); 55 | # if ( trans == tcp && ! is_reverse_scan && TRW::use_TRW_algorithm ) 56 | # TRW::check_TRW_scan(c, conn_state(c, trans), F); 57 | # } 58 | # 59 | #event connection_attempt(c: connection) 60 | # { 61 | # local trans = get_port_transport_proto(c$id$orig_p); 62 | # if ( trans == tcp && TRW::use_TRW_algorithm ) 63 | # TRW::check_TRW_scan(c, conn_state(c, trans), F); 64 | # } 65 | # 66 | #event connection_rejected(c: connection) 67 | # { 68 | # local is_reverse_scan = c$orig$state == TCP_RESET; 69 | # 70 | # local trans = get_port_transport_proto(c$id$orig_p); 71 | # if ( trans == tcp && TRW::use_TRW_algorithm ) 72 | # TRW::check_TRW_scan(c, conn_state(c, trans), is_reverse_scan); 73 | # } 74 | -------------------------------------------------------------------------------- /zkg.meta: -------------------------------------------------------------------------------- 1 | [package] 2 | description = scan detection in 2.x world. Forward porting of bro-1.5.3 scan.bro accompanied with new heuristics and quicker detections 3 | tags = scan detection 4 | version = 0.1 5 | script_dir = scripts 6 | #test_command = ( cd tests && btest -d ) 7 | --------------------------------------------------------------------------------