diff --git a/scripts/base/init-bare.zeek b/scripts/base/init-bare.zeek index d28ab9a13e..cd1914cea3 100644 --- a/scripts/base/init-bare.zeek +++ b/scripts/base/init-bare.zeek @@ -584,6 +584,22 @@ type fa_metadata: record { inferred: bool &default=T; }; +## A hook taking a connection, analyzer tag and analyzer id that can be +## used to veto disabling analyzers. This hook is invoked synchronously +## during a :zeek:see:`disable_analyzer` call. +## +## Scripts implementing this hook should have other logic that will eventually +## disable the analyzer for the given connection. That is, if a script vetoes +## disabling an analyzer, it takes responsibility for a later call to +## :zeek:see:`disable_analyzer`, which may be never. +## +## c: The connection +## +## atype: The type / tag of the analyzer being disabled. +## +## aid: The analyzer ID. +type disabling_analyzer: hook(c: connection, atype: AllAnalyzers::Tag, aid: count); + ## Fields of a SYN packet. ## ## .. zeek:see:: connection_SYN_packet diff --git a/src/zeek.bif b/src/zeek.bif index 626d66b7ac..82b4b75a8b 100644 --- a/src/zeek.bif +++ b/src/zeek.bif @@ -4590,6 +4590,15 @@ function disable_analyzer%(cid: conn_id, aid: count, err_if_no_conn: bool &defau return zeek::val_mgr->False(); } + static auto disabling_analyzer_hook = id::find_func("disabling_analyzer"); + if ( disabling_analyzer_hook ) + { + auto hook_rval = disabling_analyzer_hook->Invoke(c->GetVal(), a->GetAnalyzerTag().AsVal(), + zeek::val_mgr->Count(aid)); + if ( hook_rval && ! hook_rval->AsBool() ) + return zeek::val_mgr->False(); + } + if ( prevent ) a->Parent()->PreventChildren(a->GetAnalyzerTag()); diff --git a/testing/btest/Baseline/bifs.disable_analyzer-hook/out b/testing/btest/Baseline/bifs.disable_analyzer-hook/out new file mode 100644 index 0000000000..3c599a25f6 --- /dev/null +++ b/testing/btest/Baseline/bifs.disable_analyzer-hook/out @@ -0,0 +1,16 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +proto confirm, Analyzer::ANALYZER_HTTP +http_request, GET, /style/enhanced.css +preventing disable_analyzer, [orig_h=192.168.1.104, orig_p=1673/tcp, resp_h=63.245.209.11, resp_p=80/tcp], Analyzer::ANALYZER_HTTP, 3, 1 +F +http_reply, 200 +http_request, GET, /script/urchin.js +preventing disable_analyzer, [orig_h=192.168.1.104, orig_p=1673/tcp, resp_h=63.245.209.11, resp_p=80/tcp], Analyzer::ANALYZER_HTTP, 3, 3 +F +http_reply, 200 +http_request, GET, /images/template/screen/bullet_utility.png +allowing disable_analyzer, [orig_h=192.168.1.104, orig_p=1673/tcp, resp_h=63.245.209.11, resp_p=80/tcp], Analyzer::ANALYZER_HTTP, 3, 5 +T +total http messages, { +[[orig_h=192.168.1.104, orig_p=1673/tcp, resp_h=63.245.209.11, resp_p=80/tcp]] = 5 +} diff --git a/testing/btest/Baseline/scripts.base.protocols.ssl.prevent-disable-analyzer/.stdout b/testing/btest/Baseline/scripts.base.protocols.ssl.prevent-disable-analyzer/.stdout new file mode 100644 index 0000000000..d495e416fe --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ssl.prevent-disable-analyzer/.stdout @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +analyzer_confirmation, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], Analyzer::ANALYZER_SSL, 3 +encrypted_data, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], T, 22, 32, 1 +established, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp] +disabling_analyzer, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], Analyzer::ANALYZER_SSL, 3 +preventing disabling_analyzer, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], Analyzer::ANALYZER_SSL, 3 +encrypted_data, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], F, 22, 32, 2 +encrypted_data, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], T, 23, 31, 3 +encrypted_data, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], T, 23, 17, 4 +disabling_analyzer, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], Analyzer::ANALYZER_SSL, 3 +allowing disabling_analyzer, [orig_h=10.0.0.80, orig_p=56637/tcp, resp_h=68.233.76.12, resp_p=443/tcp], Analyzer::ANALYZER_SSL, 3 diff --git a/testing/btest/bifs/disable_analyzer-hook.zeek b/testing/btest/bifs/disable_analyzer-hook.zeek new file mode 100644 index 0000000000..252b7f253d --- /dev/null +++ b/testing/btest/bifs/disable_analyzer-hook.zeek @@ -0,0 +1,45 @@ +# @TEST-DOC: Disable the analyzer if 5 or more messages have been seen on a connection. +# @TEST-EXEC: zeek -b -r $TRACES/http/pipelined-requests.trace %INPUT >out +# @TEST-EXEC: btest-diff out + +@load base/protocols/http + +global msg_count: table[conn_id] of count &default=0; + +event analyzer_confirmation(c: connection, atype: AllAnalyzers::Tag, aid: count) &priority=10 + { + if ( atype != Analyzer::ANALYZER_HTTP ) + return; + + print "proto confirm", atype; + } + +# Prevent disabling all analyzers. +hook disabling_analyzer(c: connection, atype: AllAnalyzers::Tag, aid: count) + { + if ( msg_count[c$id] < 4 ) + { + print "preventing disable_analyzer", c$id, atype, aid, msg_count[c$id]; + break; + } + + print "allowing disable_analyzer", c$id, atype, aid, msg_count[c$id]; + } + +event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string) + { + ++msg_count[c$id]; + print "http_request", method, original_URI; + print disable_analyzer(c$id, current_analyzer(), T, T); + } + +event http_reply(c: connection, version: string, code: count, reason: string) + { + ++msg_count[c$id]; + print "http_reply", code; + } + +event zeek_done() + { + print "total http messages", msg_count; + } diff --git a/testing/btest/scripts/base/protocols/ssl/prevent-disable-analyzer.test b/testing/btest/scripts/base/protocols/ssl/prevent-disable-analyzer.test new file mode 100644 index 0000000000..e4ff1257d4 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ssl/prevent-disable-analyzer.test @@ -0,0 +1,52 @@ +# @TEST-DOC: Implement disabling_analyzer hook to keep the SSL analyzer enabled for a bit longer. +# @TEST-EXEC: zeek -b -C -r $TRACES/tls/tls1.2.trace %INPUT +# @TEST-EXEC: btest-diff .stdout + +@load base/protocols/ssl + +# This is the default, but make it explicit. +redef SSL::disable_analyzer_after_detection = T; + +redef record SSL::Info += { + encrypted_data: count &default=0; +}; + +# After how many ssl_encrypted_data events to disable the analyzer. The +# pcap triggers seven, the handshake is over after the first two. +global encrypted_data_wanted = 4; + +# Prevent disabling the SSL analyzer for this connection until we've seen encrypted_data_wanted +# encrypted data events on it. Our ssl_encrypted_data event handler has the inverse condition. +hook disabling_analyzer(c: connection, atype: AllAnalyzers::Tag, aid: count) + { + print "disabling_analyzer", c$id, atype, aid; + if ( atype != Analyzer::ANALYZER_SSL || ! c?$ssl ) + return; + + if ( c$ssl$encrypted_data < encrypted_data_wanted ) + { + print "preventing disabling_analyzer", c$id, atype, aid; + break; + } + + print "allowing disabling_analyzer", c$id, atype, aid; + } + +event ssl_established(c: connection) + { + print "established", c$id; + } + +event analyzer_confirmation(c: connection, atype: AllAnalyzers::Tag, aid: count) + { + print "analyzer_confirmation", c$id, atype, aid; + } + +event ssl_encrypted_data(c: connection, is_client: bool, record_version: count, content_type: count, length: count) + { + ++c$ssl$encrypted_data; + print "encrypted_data", c$id, is_client, content_type, length, c$ssl$encrypted_data; + + if ( c$ssl?$analyzer_id && c$ssl$encrypted_data >= encrypted_data_wanted ) + disable_analyzer(c$id, c$ssl$analyzer_id); + }