[3007] in Kerberos-V5-bugs

home help back first fref pref prev next nref lref last post

krb5-kdc/665: SAM preauth replay detection (I)

daemon@ATHENA.MIT.EDU (fcusack@iconnet.net)
Wed Nov 25 18:53:19 1998

Resent-From: gnats@rt-11.MIT.EDU (GNATS Management)
Resent-To: krb5-unassigned@RT-11.MIT.EDU
Resent-Reply-To: krb5-bugs@MIT.EDU, fcusack@iconnet.net
Date: Wed, 25 Nov 1998 18:53:01 -0500 (EST)
From: fcusack@iconnet.net
Reply-To: fcusack@iconnet.net
To: krb5-bugs@MIT.EDU
Cc: fcusack@iconnet.net


>Number:         665
>Category:       krb5-kdc
>Synopsis:       sam-response must go to the same KDC that sent sam-challenge
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    krb5-unassigned
>State:          open
>Class:          sw-bug
>Submitter-Id:   unknown
>Arrival-Date:   Wed Nov 25 18:53:00 EST 1998
>Last-Modified:
>Originator:     Frank Cusack
>Organization:
Icon CMT Corp.
>Release:        krb5-current-19981119
>Environment:
Unix
System: SunOS ratbert 5.6 Generic_105181-09 sun4u sparc SUNW,Ultra-5_10
Architecture: sun4

>Description:
	All state information for a sam-response is encoded in the
	krb5_predicted_sam_response struct. This makes it easy for
	an attacker to replay a sam-response to a different KDC.
	(Actually there is no replay detection as of yet so it's
	easy to replay to the same KDC as well; I'll submit a patch
	for that later.)

	This patch adds a kdc_id field to the predicted_sam_response,
	which is the hostid of the KDC. This will only work on systems
	that have gethostid() defined. Maybe you will want to write
	a macro to define it some other way to systems missing it.

	Also, some SAM types use multiple transactions, and those
	transactions all need to go to the same KDC. This ensures that.

	This patch depends on krb5-kdc/662, 663 and 664. This also
	fixes /663 which was incomplete.
>How-To-Repeat:
>Fix:
Index: lib/krb5/krb/kfree.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/lib/krb5/krb/kfree.c,v
retrieving revision 1.2
diff -u -r1.2 kfree.c
--- kfree.c	1998/11/25 06:50:50	1.2
+++ kfree.c	1998/11/25 23:47:21
@@ -671,8 +671,8 @@
 {
     if (!esre)
 	return;
-    if (esre->sam_passcode.data)
-	krb5_free_data_contents(ctx, &esre->sam_passcode);
+    if (esre->sam_sad.data)
+	krb5_free_data_contents(ctx, &esre->sam_sad);
 }
 
 KRB5_DLLIMP void KRB5_CALLCONV
Index: include/k5-int.h
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/include/k5-int.h,v
retrieving revision 1.3
diff -u -r1.3 k5-int.h
--- k5-int.h	1998/11/25 18:34:20	1.3
+++ k5-int.h	1998/11/25 21:38:55
@@ -353,6 +353,7 @@
 typedef struct _krb5_predicted_sam_response {
 	krb5_magic	magic;
 	krb5_keyblock	sam_key;
+	krb5_int32	kdc_id; /* some magic to avoid esre replays */
 } krb5_predicted_sam_response;
 
 typedef struct _krb5_sam_challenge {
Index: lib/krb5/asn.1/asn1_k_decode.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/lib/krb5/asn.1/asn1_k_decode.c,v
retrieving revision 1.3
diff -u -r1.3 asn1_k_decode.c
--- asn1_k_decode.c	1998/11/25 18:34:20	1.3
+++ asn1_k_decode.c	1998/11/25 21:39:01
@@ -813,6 +813,7 @@
   setup();
   { begin_structure();
     get_field(val->sam_key,0,asn1_decode_encryption_key);
+    get_field(val->kdc_id,1,asn1_decode_int32);
     end_structure();
     val->magic = KV5M_PREDICTED_SAM_RESPONSE;
   }
Index: lib/krb5/asn.1/asn1_k_encode.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/lib/krb5/asn.1/asn1_k_encode.c,v
retrieving revision 1.3
diff -u -r1.3 asn1_k_encode.c
--- asn1_k_encode.c	1998/11/25 18:34:20	1.3
+++ asn1_k_encode.c	1998/11/25 21:39:01
@@ -949,6 +949,7 @@
 {
   asn1_setup();
 
+  asn1_addfield(val->kdc_id,1,asn1_encode_integer);
   asn1_addfield(&(val->sam_key),0,asn1_encode_encryption_key);
 
   asn1_makeseq();
Index: include/k5-int.h
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/include/k5-int.h,v
retrieving revision 1.4
diff -u -r1.4 k5-int.h
--- k5-int.h	1998/11/25 21:40:28	1.4
+++ k5-int.h	1998/11/25 22:24:18
@@ -348,6 +348,7 @@
 	krb5_magic	magic;
 	krb5_keyblock	sam_key;
 	krb5_data	next_challenge;
+	krb5_int32	kdc_id; /* some magic to avoid esre replays */
 } krb5_rb1_track_data;
 
 typedef struct _krb5_predicted_sam_response {
Index: lib/krb5/asn.1/asn1_k_decode.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/lib/krb5/asn.1/asn1_k_decode.c,v
retrieving revision 1.4
diff -u -r1.4 asn1_k_decode.c
--- asn1_k_decode.c	1998/11/25 21:40:28	1.4
+++ asn1_k_decode.c	1998/11/25 22:24:23
@@ -828,6 +828,7 @@
   { begin_structure();
     opt_string(val->next_challenge, 0, asn1_decode_charstring);
     get_field(val->sam_key, 1, asn1_decode_encryption_key);
+    get_field(val->kdc_id,2,asn1_decode_int32);
     end_structure();
     val->magic = KV5M_RB1_TRACK_DATA;
   }
Index: lib/krb5/asn.1/asn1_k_encode.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/lib/krb5/asn.1/asn1_k_encode.c,v
retrieving revision 1.4
diff -u -r1.4 asn1_k_encode.c
--- asn1_k_encode.c	1998/11/25 21:40:28	1.4
+++ asn1_k_encode.c	1998/11/25 22:24:24
@@ -963,6 +963,7 @@
     int *retlen;
 {
     asn1_setup();
+    asn1_addfield(val->kdc_id,2,asn1_encode_integer);
     asn1_addfield(&(val->sam_key), 1, asn1_encode_encryption_key);
     add_optstring(val->next_challenge, 0, asn1_encode_charstring);
     asn1_makeseq();
Index: kdc/main.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/kdc/main.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 main.c
--- main.c	1998/11/24 17:05:08	1.1.1.1
+++ main.c	1998/11/25 23:33:45
@@ -28,6 +28,7 @@
 #include <signal.h>
 #include <errno.h>
 #include <netdb.h>
+#include <unistd.h>	/* gethostid() */
 
 #include "k5-int.h"
 #include "com_err.h"
@@ -58,6 +59,8 @@
 static char *kdc_current_rcname = (char *) NULL;
 static int rkey_init_done = 0;
 
+krb5_int32 kdc_id;	/* hostid for SAM replay detection */
+
 #ifdef POSIX_SIGNALS
 static struct sigaction s_action;
 #endif /* POSIX_SIGNALS */
@@ -813,6 +816,8 @@
     initialize_realms(kcontext, argc, argv);
 
     setup_signal_handlers();
+
+    kdc_id = gethostid();
 
     if ((retval = setup_network(argv[0]))) {
 	com_err(argv[0], retval, "while initializing network");
Index: kdc/preauth/pa_sam.h
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/kdc/preauth/pa_sam.h,v
retrieving revision 1.1
diff -u -r1.1 pa_sam.h
--- pa_sam.h	1998/11/25 04:06:17	1.1
+++ pa_sam.h	1998/11/25 23:33:45
@@ -25,6 +25,8 @@
 
 #include "k5-int.h"
 
+extern krb5_int32 kdc_id;
+
 /* XXX These probably need windows callconv defines? */
 typedef krb5_error_code (*sam_generate_proc)
     KRB5_PROTOTYPE((krb5_context context,
Index: kdc/preauth/pa_sam_cryptocard.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/kdc/preauth/pa_sam_cryptocard.c,v
retrieving revision 1.4
diff -u -r1.4 pa_sam_cryptocard.c
--- pa_sam_cryptocard.c	1998/11/25 21:38:40	1.4
+++ pa_sam_cryptocard.c	1998/11/25 23:33:45
@@ -248,6 +248,7 @@
 
     /* Convert the SAD into a key. */
     rb1_track_data.magic = KV5M_PREDICTED_SAM_RESPONSE;
+    rb1_track_data.kdc_id = kdc_id;
     if (retval = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5,
 				      &predict_response, 0 /* salt */,
 				      &rb1_track_data.sam_key)) {
@@ -320,7 +321,7 @@
 #endif /* 0 */
       
     if (retval = encode_krb5_sam_challenge(&sc, &scratch))
-	 goto cleanup;
+	goto cleanup;
 
     pa_data->magic = KV5M_PA_DATA;
     pa_data->pa_type = KRB5_PADATA_SAM_CHALLENGE;
@@ -377,16 +378,16 @@
 
     /* Sanity check */
     if (sr->sam_type != PA_SAM_TYPE_CRYPTOCARD) {
-	retval = KRB5KDC_ERR_PREAUTH_FAILED;
-	com_err("krb5kdc", retval, "inconsistent sam_type (RB1 expected)");
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"inconsistent sam_type (RB1 expected)");
 	goto cleanup;
     }
 
     scratch.length = sr->sam_track_id.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "rb1 track_id data not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"rb1 track_id data not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
@@ -411,11 +412,17 @@
 	goto cleanup;
     }
 
+    if (rb1_track_data->kdc_id != kdc_id) {
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"Warning - possible SAM replay attack!");
+	goto cleanup;
+    }
+
     scratch.length = sr->sam_enc_nonce_or_ts.ciphertext.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "RB1 sam_enc_nonce_or_ts not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"RB1 sam_enc_nonce_or_ts not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
Index: kdc/preauth/pa_sam_digi_path.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/kdc/preauth/pa_sam_digi_path.c,v
retrieving revision 1.4
diff -u -r1.4 pa_sam_digi_path.c
--- pa_sam_digi_path.c	1998/11/25 21:38:40	1.4
+++ pa_sam_digi_path.c	1998/11/25 23:33:45
@@ -152,6 +152,8 @@
 
     /* Convert the SAD into a key. */
     psr.magic = KV5M_PREDICTED_SAM_RESPONSE;
+    psr.kdc_id = kdc_id;
+
     if (retval = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5,
 				      &predict_response, 0 /* salt */,
 				      &psr.sam_key)) {
@@ -224,7 +226,7 @@
 #endif /* 0 */
       
     if (retval = encode_krb5_sam_challenge(&sc, &scratch))
-	 goto cleanup;
+	goto cleanup;
 
     pa_data->magic = KV5M_PA_DATA;
     pa_data->pa_type = KRB5_PADATA_SAM_CHALLENGE;
@@ -277,16 +279,16 @@
 
     /* Sanity check */
     if (sr->sam_type != PA_SAM_TYPE_DIGI_PATH) {
-	retval = KRB5KDC_ERR_PREAUTH_FAILED;
-	com_err("krb5kdc", retval, "inconsistent sam_type (snk4 expected)");
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"inconsistent sam_type (snk4 expected)");
 	goto cleanup;
     }
 
     scratch.length = sr->sam_track_id.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "snk4 track_id data not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"snk4 track_id data not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
@@ -311,11 +313,17 @@
 	goto cleanup;
     }
 
+    if (psr->kdc_id != kdc_id) {
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"Warning - possible SAM replay attack!");
+	goto cleanup;
+    }
+
     scratch.length = sr->sam_enc_nonce_or_ts.ciphertext.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "Grail sam_enc_nonce_or_ts not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"snk4 sam_enc_nonce_or_ts not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
Index: kdc/preauth/pa_sam_grail.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/kdc/preauth/pa_sam_grail.c,v
retrieving revision 1.2
diff -u -r1.2 pa_sam_grail.c
--- pa_sam_grail.c	1998/11/25 21:38:40	1.2
+++ pa_sam_grail.c	1998/11/25 23:33:45
@@ -69,6 +69,7 @@
     sc.sam_challenge.length = strlen(sc.sam_challenge.data);
 
     psr.magic = KV5M_PREDICTED_SAM_RESPONSE;
+    psr.kdc_id = kdc_id;
 
     /* For GRAIL, we ignore the sam_key, and use our own fixed key. */
     if (retval = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5,
@@ -179,16 +180,16 @@
 
     /* Sanity check */
     if (sr->sam_type != PA_SAM_TYPE_GRAIL) {
-	retval = KRB5KDC_ERR_PREAUTH_FAILED;
-	com_err("krb5kdc", retval, "inconsistent sam_type (Grail expected)");
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"inconsistent sam_type (Grail expected)");
 	goto cleanup;
     }
 
     scratch.length = sr->sam_track_id.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "Grail track_id data not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"Grail track_id data not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
@@ -213,11 +214,17 @@
 	goto cleanup;
     }
 
+    if (psr->kdc_id != kdc_id) {
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"Warning - possible SAM replay attack!");
+	goto cleanup;
+    }
+
     scratch.length = sr->sam_enc_nonce_or_ts.ciphertext.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "Grail sam_enc_nonce_or_ts not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"Grail sam_enc_nonce_or_ts not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
Index: kdc/preauth/pa_sam_securid.c
===================================================================
RCS file: /icon/d04/cvsroot/3rd-party/krb5-19981119/kdc/preauth/pa_sam_securid.c,v
retrieving revision 1.3
diff -u -r1.3 pa_sam_securid.c
--- pa_sam_securid.c	1998/11/25 21:38:40	1.3
+++ pa_sam_securid.c	1998/11/25 23:33:45
@@ -63,11 +63,7 @@
     krb5_timestamp	tl_time;		/* to avoid stale data */
     char		newpin[LENPRNST + 1];	/* for PIN verification */
     struct SD_CLIENT	sd_info;		/* SecurID state info */
-    krb5_addrtype	addrtype;		/* For verifying state info */
-    krb5_int32		addr1;			/* IPv4 address for state */
-    krb5_int32		addr2;			/* Reserved for IPv6 */
-    krb5_int32		addr3;			/* Reserved for IPv6 */
-    krb5_int32		addr4;			/* Reserved for IPv6 */
+    krb5_int32		kdc_id;			/* Keep sd_info local */
 } krb5_securid_state;
 
 union config_record configure;
@@ -99,6 +95,7 @@
     krb5_tl_data		tl_data;
     krb5_securid_state		securid_state;
     krb5_timestamp		timenow;
+    int				update_tl_data = 0;
 
     memset(&sc, 0, sizeof(sc));
     memset(&psr, 0, sizeof(psr));
@@ -133,14 +130,6 @@
 	    securid_state.state = SECURID_STATE_INITIAL;
 	}
 
-	/* XXX Get local address, compare against saved address. */
-	if (securid_state.state != SECURID_STATE_INITIAL) {
-	if (krb5_address_compare(context, addr1, addr2) == FALSE) {
-	    securid_state.state = SECURID_STATE_INITIAL;
-	}
-	}
-	*/
-
 	switch (securid_state.state) {
 	case SECURID_STATE_INITIAL:
 	    sc.sam_reponse_prompt.data = "SecurID Passcode";
@@ -171,7 +160,15 @@
     }
     sc.sam_response_prompt.length = strlen(sc.sam_challenge_label.data);
 
+    /*
+     * XXX Note that for states that require multiple transactions,
+     * the KRB_ERROR will always come from the same KDC that the
+     * first AS_REQ goes to; this is b/c this code runs right after
+     * the verify code in those states. Thus, we only have to check
+     * the kdc_id in the verify code.
+     */
     psr.magic = KV5M_PREDICTED_SAM_RESPONSE;
+    psr.kdc_id = kdc_id;
     /* Encrypt with longterm key (send-encrypted-sad). */
     krb5_copy_keyblock_contents(context, &client_key, &psr.sam_key);
 
@@ -289,16 +286,16 @@
 
     /* Sanity check */
     if (sr->sam_type != PA_SAM_TYPE_SECURID) {
-	retval = KRB5KDC_ERR_PREAUTH_FAILED;
-	com_err("krb5kdc", retval, "inconsistent sam_type (SecurID expected)");
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"inconsistent sam_type (SecurID expected)");
 	goto cleanup;
     }
 
     scratch.length = sr->sam_track_id.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "securid track_id data not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"securid track_id data not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
@@ -323,11 +320,19 @@
 	goto cleanup;
     }
 
+    if (psr->kdc_id != kdc_id) {
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"Warning - possible SAM replay attack!");
+	/* Reset state so user can try again quickly. */
+	old_state = SECURID_STATE_NONE;
+	goto update_tl_data;
+    }
+
     scratch.length = sr->sam_enc_nonce_or_ts.ciphertext.length;
     if (!scratch.length) {
-          retval = KRB5KDC_ERR_PREAUTH_FAILED;
-          com_err("krb5kdc", retval, "Grail sam_enc_nonce_or_ts not found");
-          goto cleanup;
+	com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
+		"SecurID sam_enc_nonce_or_ts not found");
+	goto cleanup;
     }
     if ((scratch.data = malloc(scratch.length)) == NULL) {
 	retval = ENOMEM;
@@ -453,7 +458,8 @@
 	     * like the new password does. Unfortunately, we can't
 	     * "simply" send back a key expired message as that is
 	     * handled by the client (we won't get the message back,
-	     * also, the prompt will be wrong.)
+	     * also, the prompt will be wrong.) Also, the PIN will
+	     * be visible when typed. :(
 	     */
 	    if (sd_dat.user_selectable == CANNOT_CHOOSE_PIN) {
 		com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED,
@@ -522,6 +528,7 @@
 	break;
     } /* switch (securid_state.state) */
 
+update_tl_data:
     /*
      * Update state information if it has changed.
      * Unlike CRYPTOCard, we might need to fail if we can't update.
@@ -536,8 +543,6 @@
 	}
 	tl_data.tl_data_length = sizeof(securid_state);
 	tl_data.tl_data_contents = &securid_state;
-
-	/* XXX add our IP address/unique info */
 
 	if (retval2 = krb5_dbe_update_tl_data(kdc_context, &assoc, &tl_data)) {
 	    com_err("krb5kdc", retval2, "while updating SecurID tl_data");
>Audit-Trail:
>Unformatted:

home help back first fref pref prev next nref lref last post