zeek/src/analyzer/protocol/websocket/WebSocket.cc
Robin Sommer 2ec44f098f
Extend PIA's FirstPacket API.
`FirstPacket()` so far supported only TCP. To extend this to UDP, we
move the method into the PIA base class; give it a protocol parameter
for the case that there's no actual packet is available; and add the
ability to create fake UDP packets as well, not just TCP.

This whole thing is pretty ugly to begin with, and this doesn't make
it nicer, but we need this extension that so we can feed UDP data into
the signature engine that's tunneled over other protocols. Without the
fake packets, DPD signatures in particular wouldn't have anything to
match on.
2024-05-07 18:19:46 +02:00

121 lines
4.5 KiB
C++

// See the file in the main distribution directory for copyright.
#include "zeek/analyzer/protocol/websocket/WebSocket.h"
#include <string_view>
#include "zeek/analyzer/Manager.h"
#include "zeek/analyzer/protocol/pia/PIA.h"
#include "zeek/analyzer/protocol/websocket/events.bif.h"
namespace zeek::analyzer::websocket {
WebSocket_Analyzer::WebSocket_Analyzer(Connection* conn) : analyzer::tcp::TCP_ApplicationAnalyzer("WebSocket", conn) {}
void WebSocket_Analyzer::Init() {
tcp::TCP_ApplicationAnalyzer::Init();
// This event gives scriptland a chance to log and configure the analyzer.
// The WebSocket analyzer ships with a handler that calls back into
// Configure(), via WebSocket::__configure_analyzer().
zeek::BifEvent::enqueue_websocket_established(this, Conn(), GetID());
}
bool WebSocket_Analyzer::Configure(zeek::RecordValPtr config) {
// TODO: Check extensions and modify parsing if needed, e.g. WebSocket frame
// compression extension: https://www.rfc-editor.org/rfc/rfc7692.html
//
// interp->SetExtensions(...)
//
// TODO: The Sec-WebSocket-Protocol header might provide some information
// that we could leverage to instantiate a more specific analyzer.
//
// For now, we just attach a PIA analyzer as child analyzer.
static const auto& config_type = id::find_type<zeek::RecordType>("WebSocket::AnalyzerConfig");
static int analyzer_idx = config_type->FieldOffset("analyzer");
static int use_dpd_idx = config_type->FieldOffset("use_dpd");
static const bool use_spicy_analyzer = id::find_val<BoolVal>("WebSocket::use_spicy_analyzer")->AsBool();
analyzer::Analyzer* effective_analyzer = nullptr;
if ( use_spicy_analyzer ) {
static const auto* component = zeek::analyzer_mgr->Lookup("SPICY_WEBSOCKET");
if ( ! component ) {
reporter->FatalError("SPICY_WEBSOCKET analyzer tag not available");
return false;
}
effective_analyzer = zeek::analyzer_mgr->InstantiateAnalyzer(component->Tag(), Conn());
if ( ! AddChildAnalyzer(effective_analyzer) )
return false;
}
else {
interp = std::make_unique<binpac::WebSocket::WebSocket_Conn>(this);
effective_analyzer = this;
}
if ( config->HasField(analyzer_idx) ) {
const auto& analyzer_tag_val = config->GetField(analyzer_idx);
auto analyzer_tag = analyzer_mgr->GetComponentTag(analyzer_tag_val.get());
if ( analyzer_tag == zeek::Tag() ) {
reporter->InternalWarning("no component tag for enum '%s'",
analyzer_tag_val->GetType<EnumType>()->Lookup(analyzer_tag_val->AsEnum()));
return false;
}
DBG_LOG(DBG_ANALYZER, "%s Configure() using analyzer %s", fmt_analyzer(this).c_str(),
analyzer_tag_val->GetType<EnumType>()->Lookup(analyzer_tag_val->AsEnum()));
auto* analyzer = analyzer_mgr->InstantiateAnalyzer(analyzer_tag, Conn());
if ( ! analyzer )
return false;
return effective_analyzer->AddChildAnalyzer(analyzer);
}
else if ( config->GetField(use_dpd_idx)->AsBool() ) {
DBG_LOG(DBG_ANALYZER, "%s Configure() enables DPD via PIA_TCP", fmt_analyzer(this).c_str());
auto* pia = new analyzer::pia::PIA_TCP(Conn());
if ( effective_analyzer->AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
return true;
}
return false;
}
// Neither analyzer nor dpd was enabled, success.
return true;
}
void WebSocket_Analyzer::DeliverStream(int len, const u_char* data, bool orig) {
analyzer::tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);
if ( had_gap ) {
DBG_LOG(DBG_ANALYZER, "Skipping data after gap len=%d orig=%d", len, orig);
return;
}
if ( interp ) {
try {
interp->NewData(orig, data, data + len);
} catch ( const binpac::Exception& e ) {
AnalyzerViolation(e.c_msg(), reinterpret_cast<const char*>(data), len);
}
}
else {
// The Spicy analyzer was attached as a child directly.
ForwardStream(len, data, orig);
}
}
void WebSocket_Analyzer::Undelivered(uint64_t seq, int len, bool orig) {
analyzer::tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
interp->NewGap(orig, len);
had_gap = true;
}
} // namespace zeek::analyzer::websocket