On Thu, Jul 7, 2022 at 4:24 PM Jacob Champion <jchamp...@timescale.com> wrote:
> So my question is this: does substituting my credentials for the admin's
> credentials let me weaken or break the transport encryption on the
> backend connection, and grab the password that I'm not supposed to have
> access to as a front-end client?

With some further research: yes, it does.

If a DBA is using a GSS encrypted tunnel to communicate to a foreign
server, accepting delegation by default means that clients will be
able to break that backend encryption at will, because the keys in use
will be under their control.

> Maybe there's some ephemeral exchange going on that makes it too hard to
> attack in practice, or some other mitigations.

There is no forward secrecy, ephemeral exchange, etc. to mitigate this [1]:

   The Kerberos protocol in its basic form does not provide perfect
   forward secrecy for communications.  If traffic has been recorded by
   an eavesdropper, then messages encrypted using the KRB_PRIV message,
   or messages encrypted using application-specific encryption under
   keys exchanged using Kerberos can be decrypted if the user's,
   application server's, or KDC's key is subsequently discovered.

So the client can decrypt backend communications that make use of its
delegated key material. (This also means that gssencmode is a lot
weaker than I expected.)

> I'm trying to avoid writing Wireshark dissector
> code, but maybe that'd be useful either way...

I did end up filling out the existing PGSQL dissector so that it could
decrypt GSSAPI exchanges (with the use of a keytab, that is). If you'd
like to give it a try, the patch, based on Wireshark 3.7.1, is
attached. Note the GPLv2 license. It isn't correct code yet, because I
didn't understand how packet reassembly worked in Wireshark when I
started writing the code, so really large GSSAPI messages that are
split across multiple TCP packets will confuse the dissector. But it's
enough to prove the concept.

To see this in action, set up an FDW connection that uses gssencmode
(so the server in the middle will need its own Kerberos credentials).
Capture traffic starting from the kinit through the query on the
foreign table. Export the client's key material into a keytab, and set
up Wireshark to use that keytab for decryption. When credential
forwarding is *not* in use, Wireshark will be able to decrypt the
initial client connection, but it won't be able to see anything inside
the foreign server connection. When credential forwarding is in use,
Wireshark will be able to decrypt both connections.

Thanks,
--Jacob

[1] https://www.rfc-editor.org/rfc/rfc4120
From 0cf31522223a6044edae42037e45aee0fc88352f Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchamp...@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

Reply via email to