From fb7799bdf62e104f7426b800a4ddfdc2f5e19795 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Wed, 24 Jan 2024 22:23:19 +0100 Subject: [PATCH] websocket: Fix opcode for continuation frames A continuation frame has the same type as the first frame, but that information wasn't used nor kept, resulting payload of continuation frames not being forwarded. The pcap was created with a fake Python server and a bit of message crafting. --- .../protocol/websocket/websocket-analyzer.pac | 2 +- .../protocol/websocket/websocket-protocol.pac | 7 ++++++ .../out | 5 ++++ .../websocket/fragmented-http-reply.pcap | Bin 0 -> 2100 bytes .../websocket/fragmented-http-reply.zeek | 23 ++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.websocket.fragmented-http-reply/out create mode 100644 testing/btest/Traces/websocket/fragmented-http-reply.pcap create mode 100644 testing/btest/scripts/base/protocols/websocket/fragmented-http-reply.zeek diff --git a/src/analyzer/protocol/websocket/websocket-analyzer.pac b/src/analyzer/protocol/websocket/websocket-analyzer.pac index fc60538161..192d913e77 100644 --- a/src/analyzer/protocol/websocket/websocket-analyzer.pac +++ b/src/analyzer/protocol/websocket/websocket-analyzer.pac @@ -89,7 +89,7 @@ refine flow WebSocket_Flow += { } // Forward text and binary data to downstream analyzers. - if ( ${chunk.hdr.opcode} == OPCODE_TEXT|| ${chunk.hdr.opcode} == OPCODE_BINARY) + if ( effective_opcode_ == OPCODE_TEXT || effective_opcode_ == OPCODE_BINARY) connection()->zeek_analyzer()->ForwardStream(data.length(), data.data(), is_orig()); diff --git a/src/analyzer/protocol/websocket/websocket-protocol.pac b/src/analyzer/protocol/websocket/websocket-protocol.pac index 1abd317859..87bff6cfcc 100644 --- a/src/analyzer/protocol/websocket/websocket-protocol.pac +++ b/src/analyzer/protocol/websocket/websocket-protocol.pac @@ -87,12 +87,14 @@ flow WebSocket_Flow(is_orig: bool) { uint64_t masking_key_idx_; uint64_t frame_payload_len_; std::array masking_key_; + uint8_t effective_opcode_; %} %init{ has_mask_ = false; masking_key_idx_ = 0; frame_payload_len_ = 0; + effective_opcode_ = OPCODE_CONTINUATION; %} function new_frame_payload(hdr: WebSocket_FrameHeader): uint64 @@ -100,6 +102,11 @@ flow WebSocket_Flow(is_orig: bool) { if ( frame_payload_len_ > 0 ) connection()->zeek_analyzer()->Weird("websocket_frame_not_consumed"); + // Update the effective_opcode for all frames + // following this one in the message. + if ( ${hdr.first_frame} ) + effective_opcode_ = ${hdr.opcode}; + frame_payload_len_ = ${hdr.payload_len}; has_mask_ = ${hdr.has_mask}; masking_key_idx_ = 0; diff --git a/testing/btest/Baseline/scripts.base.protocols.websocket.fragmented-http-reply/out b/testing/btest/Baseline/scripts.base.protocols.websocket.fragmented-http-reply/out new file mode 100644 index 0000000000..2e008b4fa2 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.websocket.fragmented-http-reply/out @@ -0,0 +1,5 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +websocket_established +http_header, T, HOST, localhost +http_header, F, SERVER, dont ask +http_header, F, CONTENT-LENGTH, 0 diff --git a/testing/btest/Traces/websocket/fragmented-http-reply.pcap b/testing/btest/Traces/websocket/fragmented-http-reply.pcap new file mode 100644 index 0000000000000000000000000000000000000000..81b5aa0d8a13384ebf8edc8c27894621cd79afa5 GIT binary patch literal 2100 zcmaKte`p(J7{}jBYquo?*NIa6)mI%%kTgkK+In4Ob*5%%ZPKNks~{!4yi3z$cWLg{ zr28kK)V1mca~tRu7+s4<@effH#+O&EL0MgG_R~%KC%+8Am%C z5$Vy!-dJZsVy%Ifq`+R^P-C5^Ar)z9DSY?IgfVLLzL!s5+al->3A24CTakixR7ZwVH>_#J9#SJ?K6C z%b?>fvXcQDi2)SbSlorzGf#1Ls(^hJd|k}~yJOoX`yDtA#z&{8uN@PAJ@Ngc$Ctj9 z@4Po-JMq`su9XkgI?paG_sq^L@4xZ+pN%tL9ml2LfTc_9i4GpX5q;@=0ILwd3hs${ zP@F&8PT|r>{1Av^Fa@@;_%Oq{S2(e@C=nu^y<#R>1TWc=KiSAOTI+IAf4kjEb_7{+ zm?cFTR%DHa)R^6xeHe~2IAk$lbZTOB zms;=NnZs!%Ah*Go*v8`1Hsw0`w2FD!a}D6%Eg-MA&;qihYze7DKrNAqpHq_sRFP9F zA(cuq6^VCC5;Mout +# @TEST-EXEC: btest-diff out + +@load base/protocols/websocket + +# Only print http_headers after the websocket_established() event +# to reduce the noise. There' a HTTP request within the WebSocket +# tunnel. +global ws = F; + +event websocket_established(c: connection, aid: count) + { + ws = T; + print "websocket_established"; + } + +event http_header(c: connection, is_orig: bool, original_name: string, name: string, val: string) + { + if ( ws ) + print "http_header", is_orig, name, val; + }