From 8ebd054abc5daaada1b44642f3e827e98141b799 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Sat, 13 Jan 2024 12:27:54 +0100 Subject: [PATCH] HTTP: Add mechanism to instantiate Upgrade analyzer When a HTTP upgrade request/reply is detected, lookup an analyzer tag from HTTP::upgrade_analyzers, or if nothing is found, attach PIA_TCP. --- scripts/base/init-bare.zeek | 11 +++++- src/analyzer/protocol/http/HTTP.cc | 62 +++++++++++++++++++++++------- src/analyzer/protocol/http/HTTP.h | 1 + 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/scripts/base/init-bare.zeek b/scripts/base/init-bare.zeek index 0d818cdf80..7ab94432d7 100644 --- a/scripts/base/init-bare.zeek +++ b/scripts/base/init-bare.zeek @@ -423,7 +423,6 @@ export { ## The full list of TCP Option fields parsed from a TCP header. type OptionList: vector of Option; } -module GLOBAL; module Tunnel; export { @@ -449,6 +448,16 @@ export { const max_changes_per_connection: count = 5 &redef; } # end export + +module HTTP; +export { + ## Lookup table for Upgrade analyzers. First, a case sensitive lookup + ## is done using the client's Upgrade header. If no match is found, + ## the all lower-case value is used. If there's still no match Zeek + ## uses dynamic protocol detection for the upgraded to protocol instead. + const upgrade_analyzers: table[string] of Analyzer::Tag &redef; +} + module GLOBAL; ## A type alias for a vector of encapsulating "connections", i.e. for when diff --git a/src/analyzer/protocol/http/HTTP.cc b/src/analyzer/protocol/http/HTTP.cc index 4a4eaca9d0..bd252e837d 100644 --- a/src/analyzer/protocol/http/HTTP.cc +++ b/src/analyzer/protocol/http/HTTP.cc @@ -12,6 +12,7 @@ #include "zeek/Event.h" #include "zeek/NetVar.h" +#include "zeek/analyzer/Manager.h" #include "zeek/analyzer/protocol/http/events.bif.h" #include "zeek/analyzer/protocol/mime/MIME.h" #include "zeek/file_analysis/Manager.h" @@ -775,12 +776,9 @@ void HTTP_Analyzer::DeliverStream(int len, const u_char* data, bool is_orig) { if ( TCP() && TCP()->IsPartial() ) return; - if ( upgraded ) - return; - - if ( pia ) { + if ( upgraded || pia ) { // There will be a PIA instance if this connection has been identified - // as a connect proxy. + // as a connect proxy, or a child analyzer if there was an upgrade. ForwardStream(len, data, is_orig); return; } @@ -1311,15 +1309,8 @@ void HTTP_Analyzer::ReplyMade(bool interrupted, const char* msg) { reply_reason_phrase = nullptr; // unanswered requests = 1 because there is no pop after 101. - if ( reply_code == 101 && unanswered_requests.size() == 1 && upgrade_connection && upgrade_protocol.size() ) { - // Upgraded connection that switches immediately - e.g. websocket. - upgraded = true; - RemoveSupportAnalyzer(content_line_orig); - RemoveSupportAnalyzer(content_line_resp); - - if ( http_connection_upgrade ) - EnqueueConnEvent(http_connection_upgrade, ConnVal(), make_intrusive(upgrade_protocol)); - } + if ( reply_code == 101 && unanswered_requests.size() == 1 && upgrade_connection && upgrade_protocol.size() ) + HTTP_Upgrade(); reply_code = 0; upgrade_connection = false; @@ -1331,6 +1322,49 @@ void HTTP_Analyzer::ReplyMade(bool interrupted, const char* msg) { reply_state = EXPECT_REPLY_LINE; } +void HTTP_Analyzer::HTTP_Upgrade() { + // Upgraded connection that switches immediately - e.g. websocket. + + // Lookup an analyzer tag in the HTTP::upgrade_analyzer table. + static const auto& upgrade_analyzers = id::find_val("HTTP::upgrade_analyzers"); + + auto upgrade_protocol_val = make_intrusive(upgrade_protocol); + auto v = upgrade_analyzers->Find(upgrade_protocol_val); + if ( ! v ) { + // If not found, try the all lower version, too. + auto lower_upgrade_protocol = util::strtolower(upgrade_protocol); + upgrade_protocol_val = make_intrusive(lower_upgrade_protocol); + v = upgrade_analyzers->Find(upgrade_protocol_val); + } + + if ( v ) { + auto analyzer_tag_val = cast_intrusive(v); + DBG_LOG(DBG_ANALYZER, "Found %s in HTTP::upgrade_analyzers for %s", + analyzer_tag_val->GetType()->Lookup(analyzer_tag_val->AsEnum()), + upgrade_protocol_val->CheckString()); + auto analyzer_tag = analyzer_mgr->GetComponentTag(analyzer_tag_val.get()); + auto* analyzer = analyzer_mgr->InstantiateAnalyzer(analyzer_tag, Conn()); + if ( analyzer ) + AddChildAnalyzer(analyzer); + } + else { + DBG_LOG(DBG_ANALYZER, "No mapping for %s in HTTP::upgrade_analyzers, using PIA instead", + upgrade_protocol.c_str()); + pia = new analyzer::pia::PIA_TCP(Conn()); + if ( AddChildAnalyzer(pia) ) { + pia->FirstPacket(true, nullptr); + pia->FirstPacket(false, nullptr); + } + } + + upgraded = true; + RemoveSupportAnalyzer(content_line_orig); + RemoveSupportAnalyzer(content_line_resp); + + if ( http_connection_upgrade ) + EnqueueConnEvent(http_connection_upgrade, ConnVal(), make_intrusive(upgrade_protocol)); +} + void HTTP_Analyzer::RequestClash(Val* /* clash_val */) { Weird("multiple_HTTP_request_elements"); diff --git a/src/analyzer/protocol/http/HTTP.h b/src/analyzer/protocol/http/HTTP.h index 016ba4f2e1..62f519201d 100644 --- a/src/analyzer/protocol/http/HTTP.h +++ b/src/analyzer/protocol/http/HTTP.h @@ -220,6 +220,7 @@ protected: void HTTP_Request(); void HTTP_Reply(); + void HTTP_Upgrade(); void RequestMade(bool interrupted, const char* msg); void ReplyMade(bool interrupted, const char* msg);