The per-message extension is popular and many clients and servers default to enabling it. Zeek does not transparently decompress such WebSocket messages/frames, but it'd be nice if it did.
RFC: https://datatracker.ietf.org/doc/html/rfc7692
This issue is to capture extending the BinPAC and Spicy WebSocket analyzers to support the per-message extension. Because the extension is negotiated during the WebSocket handshake in the HTTP analyzer, this information needs to be somehow passed to the analyzer instances. The entry point for this is here:
|
function __configure_analyzer%(c: connection, aid: count, config: WebSocket::AnalyzerConfig%): bool |
|
%{ |
|
auto* analyzer = c->FindAnalyzer(aid); |
|
auto* ws_analyzer = dynamic_cast<analyzer::websocket::WebSocket_Analyzer*>(analyzer); |
|
if ( ! ws_analyzer ) |
|
{ |
|
reporter->Warning("WebSocket analyzer to configure not found"); |
|
return zeek::val_mgr->False(); |
|
} |
|
|
|
static const auto& config_type = zeek::id::find_type<zeek::RecordType>("WebSocket::AnalyzerConfig"); |
|
|
|
if ( config->GetType() != config_type ) |
|
{ |
|
reporter->Warning("config has wrong type %s, expected %s", |
|
config->GetType()->GetName().c_str(), |
|
config_type->GetName().c_str()); |
|
return zeek::val_mgr->False(); |
|
} |
|
|
|
if ( ! ws_analyzer->Configure({zeek::NewRef{}, config->AsRecordVal()}) ) |
|
return zeek::val_mgr->False(); |
|
|
|
return zeek::val_mgr->True(); |
|
%} |
|
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; |
|
} |
For BinPAC the analyzer/interpreter class can probably be extended via C++ functions and members to configure that, for Spicy it'd need some research how to pull it off. The Spicy SSL analyzer may have a pattern, or can be skipped/unsupported initially if too involved.
Approach:
- Review existing analyzers.
- Collect/create a few pcaps where client and server negotiate the per-message extension.
- Figure out how to pass the extension negotiation result (from the HTTP headers parsed in the HTTP analyzer) down to the WebSocket analyzer (e.g. analyzer->EnablePerMessageCompression()`).
- Extend the analyzers and add tests that show websocket_frame_data()` contains decompressed payload and that forwarding to downstream analyzers or DPD also uses decompressed payload.
The per-message extension is popular and many clients and servers default to enabling it. Zeek does not transparently decompress such WebSocket messages/frames, but it'd be nice if it did.
RFC: https://datatracker.ietf.org/doc/html/rfc7692
This issue is to capture extending the BinPAC and Spicy WebSocket analyzers to support the per-message extension. Because the extension is negotiated during the WebSocket handshake in the HTTP analyzer, this information needs to be somehow passed to the analyzer instances. The entry point for this is here:
zeek/src/analyzer/protocol/websocket/functions.bif
Lines 22 to 46 in e246643
zeek/src/analyzer/protocol/websocket/WebSocket.cc
Lines 42 to 92 in e246643
For BinPAC the analyzer/interpreter class can probably be extended via C++ functions and members to configure that, for Spicy it'd need some research how to pull it off. The Spicy SSL analyzer may have a pattern, or can be skipped/unsupported initially if too involved.
Approach: