diff --git a/CHANGES b/CHANGES index 7a82f279bf..376d237f39 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,12 @@ +6.2.0-dev.468 | 2024-01-25 12:21:24 +0100 + + * websocket: Fix opcode for continuation frames (Arne Welzel, Corelight) + + 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. + 6.2.0-dev.465 | 2024-01-24 15:25:16 -0800 * Prepare Broker manager for broker::variant (Dominik Charousset) diff --git a/VERSION b/VERSION index 1d483ef0a2..974f78b7fe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.2.0-dev.465 +6.2.0-dev.468 diff --git a/src/analyzer/protocol/websocket/websocket-analyzer.pac b/src/analyzer/protocol/websocket/websocket-analyzer.pac index a1d9c30b18..07886ca1d7 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 0000000000..81b5aa0d8a Binary files /dev/null and b/testing/btest/Traces/websocket/fragmented-http-reply.pcap differ diff --git a/testing/btest/scripts/base/protocols/websocket/fragmented-http-reply.zeek b/testing/btest/scripts/base/protocols/websocket/fragmented-http-reply.zeek new file mode 100644 index 0000000000..941ad53af2 --- /dev/null +++ b/testing/btest/scripts/base/protocols/websocket/fragmented-http-reply.zeek @@ -0,0 +1,23 @@ +# @TEST-DOC: Test a HTTP request tunneled within WebSocket where the HTTP reply is fragmented. This wasn't handled properly in the first iteration. +# +# @TEST-EXEC: zeek -b -r $TRACES/websocket/fragmented-http-reply.pcap %INPUT >out +# @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; + } diff --git a/testing/external/commit-hash.zeek-testing b/testing/external/commit-hash.zeek-testing index 64e24e5e4b..79d8621fbe 100644 --- a/testing/external/commit-hash.zeek-testing +++ b/testing/external/commit-hash.zeek-testing @@ -1 +1 @@ -3577ad0657c0cd61222d329a840e881dacf93f07 +43766922ec10cb7cf41d6b15156a8b5984808a44