aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikael Kanstrup <mikael.kanstrup@axis.com>2021-10-05 23:05:24 +0200
committerWireshark GitLab Utility <gerald+gitlab-utility@wireshark.org>2021-10-15 13:23:50 +0000
commitfbeb33ee99cf364c5d7bd0f77f9128645f6893c9 (patch)
tree2ed05e318510700ba06744523999d21725e1cf7c
parentc484ad0e5c6cadcda02a7079aa53b76be418c391 (diff)
Websocket frame reassembly support
Add support for websocket fragmented payload reassembly.
-rw-r--r--epan/dissectors/packet-websocket.c169
1 files changed, 152 insertions, 17 deletions
diff --git a/epan/dissectors/packet-websocket.c b/epan/dissectors/packet-websocket.c
index 8b85f66113..d8d9eba6fd 100644
--- a/epan/dissectors/packet-websocket.c
+++ b/epan/dissectors/packet-websocket.c
@@ -11,12 +11,14 @@
*/
#include "config.h"
+#include <wsutil/wslog.h>
#include <epan/conversation.h>
#include <epan/proto_data.h>
#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/prefs.h>
+#include <epan/reassemble.h>
#include <wsutil/strtoi.h>
#include "packet-http.h"
@@ -46,6 +48,8 @@ static dissector_handle_t sip_handle;
#define WEBSOCKET_JSON 2
#define WEBSOCKET_SIP 3
+#define OPCODE_KEY 0
+
static gint pref_text_type = WEBSOCKET_NONE;
static gboolean pref_decompress = TRUE;
@@ -60,6 +64,8 @@ typedef struct {
z_streamp server_take_over_context;
z_streamp client_take_over_context;
#endif
+ guint32 frag_id;
+ guint8 first_frag_opcode;
} websocket_conv_t;
#ifdef HAVE_ZLIB
@@ -91,11 +97,22 @@ static int hf_ws_payload_close_reason = -1;
static int hf_ws_payload_ping = -1;
static int hf_ws_payload_pong = -1;
static int hf_ws_payload_unknown = -1;
+static int hf_ws_fragments = -1;
+static int hf_ws_fragment = -1;
+static int hf_ws_fragment_overlap = -1;
+static int hf_ws_fragment_overlap_conflict = -1;
+static int hf_ws_fragment_multiple_tails = -1;
+static int hf_ws_fragment_too_long_fragment = -1;
+static int hf_ws_fragment_error = -1;
+static int hf_ws_fragment_count = -1;
+static int hf_ws_reassembled_length = -1;
static gint ett_ws = -1;
static gint ett_ws_pl = -1;
static gint ett_ws_mask = -1;
static gint ett_ws_control_close = -1;
+static gint ett_ws_fragments = -1;
+static gint ett_ws_fragment = -1;
static expert_field ei_ws_payload_unknown = EI_INIT;
static expert_field ei_ws_decompression_failed = EI_INIT;
@@ -141,10 +158,31 @@ static const value_string ws_close_status_code_vals[] = {
{ 0, NULL}
};
+static const fragment_items ws_frag_items = {
+ &ett_ws_fragments,
+ &ett_ws_fragment,
+
+ &hf_ws_fragments,
+ &hf_ws_fragment,
+ &hf_ws_fragment_overlap,
+ &hf_ws_fragment_overlap_conflict,
+ &hf_ws_fragment_multiple_tails,
+ &hf_ws_fragment_too_long_fragment,
+ &hf_ws_fragment_error,
+ &hf_ws_fragment_count,
+ NULL,
+ &hf_ws_reassembled_length,
+ /* Reassembled data field */
+ NULL,
+ "websocket fragments"
+};
+
static dissector_table_t port_subdissector_table;
static dissector_table_t protocol_subdissector_table;
static heur_dissector_list_t heur_subdissector_list;
+static reassembly_table ws_reassembly_table;
+
#define MAX_UNMASKED_LEN (1024 * 256)
static tvbuff_t *
tvb_unmasked(tvbuff_t *tvb, packet_info *pinfo, const guint offset, guint payload_length, const guint8 *masking_key)
@@ -321,6 +359,11 @@ dissect_websocket_data_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
dissector_handle_t handle = NULL;
heur_dtbl_entry_t *hdtbl_entry;
+ if (pinfo->fragmented) {
+ /* Skip dissecting fragmented payload data. */
+ return;
+ }
+
/* try to find a dissector which accepts the data. */
if (websocket_conv->subprotocol) {
handle = dissector_get_string_handle(protocol_subdissector_table, websocket_conv->subprotocol);
@@ -464,12 +507,13 @@ websocket_parse_extensions(websocket_conv_t *websocket_conv, const char *str)
}
static void
-dissect_websocket_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *ws_tree, guint8 opcode, websocket_conv_t *websocket_conv, gboolean pmc, gint raw_offset)
+dissect_websocket_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *ws_tree, guint8 fin, guint8 opcode, websocket_conv_t *websocket_conv, gboolean pmc, gint raw_offset)
{
const guint offset = 0, length = tvb_reported_length(tvb);
proto_item *ti;
proto_tree *pl_tree;
tvbuff_t *tvb_appdata;
+ tvbuff_t *frag_tvb = NULL;
/* Payload */
ti = proto_tree_add_item(ws_tree, hf_ws_payload, tvb, offset, length, ENC_NA);
@@ -478,30 +522,70 @@ dissect_websocket_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, p
/* Extension Data */
/* TODO: Add dissector of Extension (not extension available for the moment...) */
-
- /* Application Data */
- if (opcode == WS_CONTINUE) {
- proto_tree_add_item(tree, hf_ws_payload_continue, tvb, offset, length, ENC_NA);
- /* TODO: Add Fragmentation support (needs FIN bit)
- * https://tools.ietf.org/html/rfc6455#section-5.4 */
+ if (opcode & 8) { /* Control frames have MSB set. */
+ dissect_websocket_control_frame(tvb, pinfo, pl_tree, opcode);
return;
}
- /* Right now this is exactly the same, this may change when exts. are added.
- tvb_appdata = tvb_new_subset_length_caplen(tvb, offset, length, length);
- */
- tvb_appdata = tvb;
- if (opcode & 8) { /* Control frames have MSB set. */
- dissect_websocket_control_frame(tvb_appdata, pinfo, pl_tree, opcode);
+ bool save_fragmented = pinfo->fragmented;
+
+ if (!fin || opcode == WS_CONTINUE) {
+ /* Fragmented data frame */
+ fragment_head *frag_msg;
+
+ pinfo->fragmented = TRUE;
+
+ if (!PINFO_FD_VISITED(pinfo) && opcode != WS_CONTINUE) {
+ /* First fragment, temporarily save opcode needed when dissecting the reassembled frame */
+ websocket_conv->first_frag_opcode = opcode;
+ }
+
+ frag_msg = fragment_add_seq_next(&ws_reassembly_table, tvb, offset,
+ pinfo, websocket_conv->frag_id,
+ NULL, tvb_captured_length_remaining(tvb, offset),
+ !fin);
+ frag_tvb = process_reassembled_data(tvb, offset, pinfo,
+ "Reassembled Message", frag_msg, &ws_frag_items,
+ NULL, tree);
+ }
+
+ if (!PINFO_FD_VISITED(pinfo) && frag_tvb) {
+ /* First time fragments fully reassembled, store opcode from first fragment */
+ p_add_proto_data(wmem_file_scope(), pinfo, proto_websocket, OPCODE_KEY,
+ GUINT_TO_POINTER(websocket_conv->first_frag_opcode));
+ }
+
+ if (frag_tvb) {
+ /* Fragments were fully reassembled. */
+ tvb_appdata = frag_tvb;
+
+ /* Lookup opcode from first fragment */
+ guint first_frag_opcode = GPOINTER_TO_UINT(
+ p_get_proto_data(wmem_file_scope(),pinfo, proto_websocket, OPCODE_KEY));
+ opcode = (guint8)first_frag_opcode;
} else {
- dissect_websocket_data_frame(tvb_appdata, pinfo, tree, pl_tree, opcode, websocket_conv, pmc, raw_offset);
+ /* Right now this is exactly the same, this may change when exts. are added.
+ tvb_appdata = tvb_new_subset_length_caplen(tvb, offset, length, length);
+ */
+ tvb_appdata = tvb;
+ }
+
+ /* Application Data */
+
+ if (pinfo->fragmented && opcode == WS_CONTINUE) {
+ /* Not last fragment, dissect continue fragment as is */
+ proto_tree_add_item(tree, hf_ws_payload_continue, tvb_appdata, offset, length, ENC_NA);
+ return;
}
-}
+ dissect_websocket_data_frame(tvb_appdata, pinfo, tree, pl_tree, opcode, websocket_conv, pmc, raw_offset);
+ pinfo->fragmented = save_fragmented;
+}
static int
dissect_websocket_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
+ static guint32 frag_id_counter = 0;
proto_item *ti, *ti_len;
guint8 fin, opcode;
gboolean mask;
@@ -522,6 +606,7 @@ dissect_websocket_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, voi
websocket_conv = (websocket_conv_t *)conversation_get_proto_data(conv, proto_websocket);
if (!websocket_conv) {
websocket_conv = wmem_new0(wmem_file_scope(), websocket_conv_t);
+ websocket_conv->frag_id = ++frag_id_counter;
http_conv_t *http_conv = (http_conv_t *)conversation_get_proto_data(conv, proto_http);
if (http_conv) {
@@ -572,7 +657,7 @@ dissect_websocket_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, voi
proto_tree_add_item(ws_tree, hf_ws_opcode, tvb, 0, 1, ENC_BIG_ENDIAN);
opcode = tvb_get_guint8(tvb, 0) & MASK_WS_OPCODE;
col_append_fstr(pinfo->cinfo, COL_INFO, " %s", val_to_str_const(opcode, ws_opcode_vals, "Unknown Opcode"));
- col_append_str(pinfo->cinfo, COL_INFO, fin ? " [FIN]" : " ");
+ col_append_str(pinfo->cinfo, COL_INFO, fin ? " [FIN]" : "[FRAGMENT] ");
/* Add Mask bit to the tree */
proto_tree_add_item(ws_tree, hf_ws_mask, tvb, 1, 1, ENC_NA);
@@ -605,7 +690,7 @@ dissect_websocket_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, voi
} else {
tvb_payload = tvb_new_subset_length_caplen(tvb, payload_offset, payload_length, payload_length);
}
- dissect_websocket_payload(tvb_payload, pinfo, tree, ws_tree, opcode, websocket_conv, pmc, tvb_raw_offset(tvb));
+ dissect_websocket_payload(tvb_payload, pinfo, tree, ws_tree, fin, opcode, websocket_conv, pmc, tvb_raw_offset(tvb));
}
return tvb_captured_length(tvb);
@@ -801,6 +886,52 @@ proto_register_websocket(void)
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
+ /* Reassembly */
+ { &hf_ws_fragments,
+ { "Reassembled websocket Fragments", "websocket.fragments",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "Fragments", HFILL }
+ },
+ { &hf_ws_fragment,
+ { "Websocket Fragment", "websocket.fragment",
+ FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_ws_fragment_overlap,
+ { "Fragment overlap", "websocket.fragment.overlap",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ "Fragment overlaps with other fragments", HFILL }
+ },
+ { &hf_ws_fragment_overlap_conflict,
+ { "Conflicting data in fragment overlap", "websocket.fragment.overlap.conflict",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ "Overlapping fragments contained conflicting data", HFILL }
+ },
+ { &hf_ws_fragment_multiple_tails,
+ { "Multiple tail fragments found", "websocket.fragment.multipletails",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ "Several tails were found when defragmenting the packet", HFILL }
+ },
+ { &hf_ws_fragment_too_long_fragment,
+ { "Fragment too long", "websocket.fragment.toolongfragment",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ "Fragment contained data past end of packet", HFILL }
+ },
+ { &hf_ws_fragment_error,
+ { "Defragmentation error", "websocket.fragment.error",
+ FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+ "Defragmentation error due to illegal fragments", HFILL }
+ },
+ { &hf_ws_fragment_count,
+ { "Fragment count", "websocket.fragment.count",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_ws_reassembled_length,
+ { "Reassembled websocket Payload length", "websocket.reassembled.length",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "The total length of the reassembled payload", HFILL }
+ },
};
@@ -809,6 +940,8 @@ proto_register_websocket(void)
&ett_ws_pl,
&ett_ws_mask,
&ett_ws_control_close,
+ &ett_ws_fragment,
+ &ett_ws_fragments,
};
static ei_register_info ei[] = {
@@ -843,6 +976,8 @@ proto_register_websocket(void)
protocol_subdissector_table = register_dissector_table("ws.protocol",
"Negotiated WebSocket protocol", proto_websocket, FT_STRING, BASE_NONE);
+ reassembly_table_register(&ws_reassembly_table, &addresses_reassembly_table_functions);
+
proto_register_field_array(proto_websocket, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
expert_websocket = expert_register_protocol(proto_websocket);