From 0cf31522223a6044edae42037e45aee0fc88352f Mon Sep 17 00:00:00 2001 From: Jacob Champion Date: Wed, 14 Sep 2022 13:08:22 -0700 Subject: [PATCH] pgsql: decrypt GSS-encrypted channels and tokens if possible License: GPLv2 Add dissection for a conversation that starts with a GSSAPI encryption request, and pass those packets along to the gssapi dissector. This allows parts of the conversation to be decrypted if Wireshark has been set up with a KRB5 keytab. Additionally, break apart the PGSQL_AUTH_GSSAPI_SSPI_DATA case into separate GSSAPI and SSPI cases, and when we're using GSSAPI, dissect the authentication tokens that are passed between the client and server. This incidentally fixes an off-by-four bug in the PGSQL_AUTH_TYPE_GSSAPI_SSPI_CONTINUE case; the offset was not being updated correctly before. The new code copies the dissection strategy of multipart, and adds a new last_nongss_frame marker similar to the STARTTLS handling code's last_nontls_frame. --- epan/dissectors/packet-pgsql.c | 121 +++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 5 deletions(-) diff --git a/epan/dissectors/packet-pgsql.c b/epan/dissectors/packet-pgsql.c index c935eec1f8..7efe1c003f 100644 --- a/epan/dissectors/packet-pgsql.c +++ b/epan/dissectors/packet-pgsql.c @@ -14,6 +14,8 @@ #include +#include "packet-dcerpc.h" +#include "packet-gssapi.h" #include "packet-tls-utils.h" #include "packet-tcp.h" @@ -22,6 +24,7 @@ void proto_reg_handoff_pgsql(void); static dissector_handle_t pgsql_handle; static dissector_handle_t tls_handle; +static dissector_handle_t gssapi_handle; static int proto_pgsql = -1; static int hf_frontend = -1; @@ -80,10 +83,13 @@ static int hf_constraint_name = -1; static int hf_file = -1; static int hf_line = -1; static int hf_routine = -1; +static int hf_gssapi_length = -1; static gint ett_pgsql = -1; static gint ett_values = -1; +static expert_field ei_gssapi_decryption_not_possible = EI_INIT; + #define PGSQL_PORT 5432 static gboolean pgsql_desegment = TRUE; static gboolean first_message = TRUE; @@ -92,11 +98,14 @@ typedef enum { PGSQL_AUTH_STATE_NONE, /* No authentication seen or used */ PGSQL_AUTH_SASL_REQUESTED, /* Server sends SASL auth request with supported SASL mechanisms*/ PGSQL_AUTH_SASL_CONTINUE, /* Server and/or client send further SASL challange-response messages */ - PGSQL_AUTH_GSSAPI_SSPI_DATA, /* GSSAPI/SSPI in use */ + PGSQL_AUTH_GSSAPI, /* GSSAPI in use */ + PGSQL_AUTH_SSPI, /* SSPI in use */ } pgsql_auth_state_t; typedef struct pgsql_conn_data { gboolean ssl_requested; + gboolean gss_requested; + guint32 last_nongss_frame; pgsql_auth_state_t auth_state; /* Current authentication state */ } pgsql_conn_data_t; @@ -189,6 +198,22 @@ static const value_string format_vals[] = { { 0, NULL } }; +static void +dissect_gssapi_data(tvbuff_t *tvb, gint offset, guint len, packet_info *pinfo, + proto_tree *tree, gssapi_encrypt_info_t *encrypt) +{ + tvbuff_t *gssapi_tvb; + guint8 *data; + + DISSECTOR_ASSERT(tvb_bytes_exist(tvb, offset, len)); + + data = (guint8 *)tvb_memdup(pinfo->pool, tvb, offset, len); + gssapi_tvb = tvb_new_child_real_data(tvb, data, len, len); + + add_new_data_source(pinfo, gssapi_tvb, "GSSAPI Data"); + call_dissector_with_data(gssapi_handle, gssapi_tvb, pinfo, tree, encrypt); +} + static void dissect_pgsql_fe_msg(guchar type, guint length, tvbuff_t *tvb, gint n, proto_tree *tree, packet_info *pinfo, pgsql_conn_data_t *conv_data) @@ -220,7 +245,12 @@ static void dissect_pgsql_fe_msg(guchar type, guint length, tvbuff_t *tvb, proto_tree_add_item(tree, hf_sasl_auth_data, tvb, n, length-4, ENC_NA); break; - case PGSQL_AUTH_GSSAPI_SSPI_DATA: + case PGSQL_AUTH_GSSAPI: + proto_tree_add_item(tree, hf_gssapi_sspi_data, tvb, n, length-4, ENC_NA); + dissect_gssapi_data(tvb, n, length-4, pinfo, tree, NULL); + break; + + case PGSQL_AUTH_SSPI: proto_tree_add_item(tree, hf_gssapi_sspi_data, tvb, n, length-4, ENC_NA); break; @@ -356,6 +386,12 @@ static void dissect_pgsql_fe_msg(guchar type, guint length, tvbuff_t *tvb, conv_data->ssl_requested = TRUE; break; + /* GSS request */ + case 80877104: + /* Next reply will be a single byte. */ + conv_data->gss_requested = TRUE; + break; + /* Cancellation request */ case 80877102: proto_tree_add_item(tree, hf_pid, tvb, n, 4, ENC_BIG_ENDIAN); @@ -430,9 +466,17 @@ static void dissect_pgsql_be_msg(guchar type, guint length, tvbuff_t *tvb, siz = (auth_type == PGSQL_AUTH_TYPE_CRYPT ? 2 : 4); proto_tree_add_item(tree, hf_salt, tvb, n, siz, ENC_NA); break; + case PGSQL_AUTH_TYPE_GSSAPI: + conv_data->auth_state = PGSQL_AUTH_GSSAPI; + break; + case PGSQL_AUTH_TYPE_SSPI: + conv_data->auth_state = PGSQL_AUTH_SSPI; + break; case PGSQL_AUTH_TYPE_GSSAPI_SSPI_CONTINUE: - conv_data->auth_state = PGSQL_AUTH_GSSAPI_SSPI_DATA; + n += 4; proto_tree_add_item(tree, hf_gssapi_sspi_data, tvb, n, length-8, ENC_NA); + if (conv_data->auth_state == PGSQL_AUTH_GSSAPI) + dissect_gssapi_data(tvb, n, length-8, pinfo, tree, NULL); break; case PGSQL_AUTH_TYPE_SASL: conv_data->auth_state = PGSQL_AUTH_SASL_REQUESTED; @@ -665,6 +709,8 @@ dissect_pgsql_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat if (!conn_data) { conn_data = wmem_new(wmem_file_scope(), pgsql_conn_data_t); conn_data->ssl_requested = FALSE; + conn_data->gss_requested = FALSE; + conn_data->last_nongss_frame = G_MAXUINT32; conn_data->auth_state = PGSQL_AUTH_STATE_NONE; conversation_add_proto_data(conversation, proto_pgsql, conn_data); } @@ -703,7 +749,8 @@ dissect_pgsql_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat case PGSQL_AUTH_SASL_CONTINUE: typestr = "SASLResponse message"; break; - case PGSQL_AUTH_GSSAPI_SSPI_DATA: + case PGSQL_AUTH_GSSAPI: /* fallthrough */ + case PGSQL_AUTH_SSPI: typestr = "GSSResponse message"; break; default: @@ -746,6 +793,18 @@ dissect_pgsql_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat return tvb_captured_length(tvb); } +static void +dissect_gssenc_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gssapi_encrypt_info_t *encrypt) +{ + gint offset = 0, len; + + proto_tree_add_item(tree, hf_gssapi_length, tvb, offset, 4, ENC_BIG_ENDIAN); + offset += 4; + len = tvb_reported_length_remaining(tvb, offset); + + dissect_gssapi_data(tvb, offset, len, pinfo, tree, encrypt); +} + /* This function is called once per TCP packet. It sets COL_PROTOCOL and * identifies FE/BE messages by adding a ">" or "<" to COL_INFO. Then it * arranges for each message to be dissected individually. */ @@ -782,6 +841,43 @@ dissect_pgsql(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) return tvb_captured_length(tvb); } + if (conn_data && conn_data->gss_requested) { + /* Response to GSS request. */ + switch (tvb_get_guint8(tvb, 0)) { + case 'G': /* Willing to perform GSS encryption */ + /* Next packet will start using GSS encryption. */ + conn_data->last_nongss_frame = pinfo->num; + break; + case 'N': /* Unwilling to perform GSS encryption */ + default: /* ErrorMessage when server does not support SSL. */ + /* TODO: maybe add expert info here? */ + break; + } + conn_data->gss_requested = FALSE; + return tvb_captured_length(tvb); + } + + if (conn_data && (pinfo->num > conn_data->last_nongss_frame)) { + tvbuff_t *subtvb; + gssapi_encrypt_info_t encrypt; + + memset(&encrypt, 0, sizeof(encrypt)); + encrypt.decrypt_gssapi_tvb = DECRYPT_GSSAPI_NORMAL; + + dissect_gssenc_msg(tvb, pinfo, tree, &encrypt); + + if (encrypt.gssapi_decrypted_tvb){ + subtvb = encrypt.gssapi_decrypted_tvb; + tcp_dissect_pdus(subtvb, pinfo, tree, pgsql_desegment, 5, + pgsql_length, dissect_pgsql_msg, data); + } else if (encrypt.gssapi_encrypted_tvb) { + subtvb = encrypt.gssapi_encrypted_tvb; + proto_tree_add_expert(tree, pinfo, &ei_gssapi_decryption_not_possible, subtvb, 0, -1); + } + + return tvb_captured_length(tvb); + } + tcp_dissect_pdus(tvb, pinfo, tree, pgsql_desegment, 5, pgsql_length, dissect_pgsql_msg, data); return tvb_captured_length(tvb); @@ -1021,7 +1117,12 @@ proto_register_pgsql(void) { &hf_routine, { "Routine", "pgsql.routine", FT_STRINGZ, BASE_NONE, NULL, 0, "The routine that reported an error.", HFILL } - } + }, + { &hf_gssapi_length, + { "Length of GSSAPI encrypted data", "pgsql.gss.length", FT_UINT32, BASE_DEC, NULL, 0, + "The length of the GSSAPI encrypted blob.", + HFILL } + }, }; static gint *ett[] = { @@ -1029,9 +1130,18 @@ proto_register_pgsql(void) &ett_values }; + expert_module_t* expert_pgsql; + + static ei_register_info ei[] = { + { &ei_gssapi_decryption_not_possible, { "pgsql.gss.decryption_not_possible", PI_UNDECODED, PI_WARN, "The PGSQL dissector could not decrypt the message.", EXPFILL }}, + }; + proto_pgsql = proto_register_protocol("PostgreSQL", "PGSQL", "pgsql"); proto_register_field_array(proto_pgsql, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); + + expert_pgsql = expert_register_protocol(proto_pgsql); + expert_register_field_array(expert_pgsql, ei, array_length(ei)); } void @@ -1042,6 +1152,7 @@ proto_reg_handoff_pgsql(void) dissector_add_uint_with_preference("tcp.port", PGSQL_PORT, pgsql_handle); tls_handle = find_dissector_add_dependency("tls", proto_pgsql); + gssapi_handle = find_dissector_add_dependency("gssapi", proto_pgsql); } /* -- 2.25.1