GPLv2-Wireshark3.7.1-pgsql-decrypt-GSS.patch.txt

text/plain

Filename: GPLv2-Wireshark3.7.1-pgsql-decrypt-GSS.patch.txt
Type: text/plain
Part: 0
Message: Re: Kerberos delegation support in libpq and postgres_fdw
From 0cf31522223a6044edae42037e45aee0fc88352f Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
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 <epan/packet.h>
 
+#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