[2403] in Kerberos-V5-bugs

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

pending/155: security concern with using home directory for .k5login

daemon@ATHENA.MIT.EDU (mhpower@MIT.EDU)
Tue Nov 5 23:43:15 1996

Resent-From: gnats@rt-11.MIT.EDU (GNATS Management)
Resent-To: gnats-admin@rt-11.MIT.EDU
Resent-Reply-To: krb5-bugs@MIT.EDU, mhpower@MIT.EDU
Date: Tue, 05 Nov 1996 23:42:23 EST
From: mhpower@MIT.EDU
To: krb5-bugs@MIT.EDU


>Number:         155
>Category:       pending
>Synopsis:       security concern with using home directory for .k5login
>Confidential:   yes
>Severity:       serious
>Priority:       medium
>Responsible:    gnats-admin
>State:          open
>Class:          sw-bug
>Submitter-Id:   unknown
>Arrival-Date:   Tue Nov 05 23:43:01 EST 1996
>Last-Modified:
>Originator:
>Organization:
>Release:
>Environment:
>Description:
>How-To-Repeat:
>Fix:
>Audit-Trail:
>Unformatted:
The kuserok function determines login authorization by reading a file
from the directory specified by the user's passwd pw_dir field on the
target machine. In many environments, an attacker may be able to cause
the reading of the file to deliver contents that were not intended by
anyone with legitimate access to the target account. I believe that,
in these environments, security can be improved substantially by
allowing Kerberos to be configured such that a directory different
from the user's home directory is used for .k5login files.

The most common reason that this type of attack is possible is that
the user's home directory is located on a remote filesystem, and
authentication of file reads is either completely absent, or is based
on weak mechanisms such as IP addresses. An attacker can begin a
remote-login attempt to a target machine, and at about the same time
inject onto the network packets from the remote fileserver's IP
address that are sufficient to convince the target machine that it has
read the relevant .k5login file. The injected packets would include
text that lists the attacker's Kerberos principal, and thus the target
machine would allow the remote login.

The attack is also possible in an especially degenerate environment in
which any user can mount an arbitrary filesystem of his choice on the
directory specified by another user's pw_dir field.

I've included patches that address this problem by allowing the
pathname for .k5login files to be specified in krb5.conf. One
potential use involves setting up a directory tree on a target
machine's local disk, e.g., each user's .k5login file would be looked
for in /var/userauth/username/.k5login, and users' home directories
would still remain on NFS or AFS filesystems. For sites with too many
users or too many target machines for this to be practical, one might
instead arrange for the specified .k5login pathname to not exist (or
to exist only for root's account), thus most likely restricting users
to use only one Kerberos principal for remote logins, which could only
be to their own accounts. This would probably be appropriate for
target machines that are intended for general timesharing service.

I've also included a related patch that allows disabling the default
access policy that's implemented in the absence of a .k5login file
(i.e., access is granted if the Kerberos principal matches the local
username). This can be useful if there are only a few legitimate
users, but a lot of default accounts (e.g., operator, news, uucp) that
might otherwise need .k5login files in order to block access.

The patches make no attempt to address the issue of .k5users files.

The patches haven't been tested at all, they may be incomplete or have
other problems, and possibly the patched code won't even compile.

Matt


*** krb5-beta7/src/config-files/krb5.conf.M.old	Tue Sep 10 20:26:07 1996
--- krb5-beta7/src/config-files/krb5.conf.M	Tue Nov  5 21:04:48 1996
***************
*** 144,149 ****
--- 144,169 ----
  do not support the default cache as created by this version of
  Kerberos. Use a value of 1 on DCE 1.0.3a systems, and a value of 2 on
  DCE 1.1 systems.
+ 
+ .IP k5login_location
+ This relation identifies a partial pathname used in locating a user's
+ .k5login file. If this relation is unspecified, the location for a
+ user's .k5login file is the top level of his home directory. If the
+ partial pathname begins with a '/', the full pathname of the .k5login
+ file is formed by concatenating this partial pathname with a "/",
+ followed by the user's login name, another "/", and the string
+ ".k5login". Otherwise, the full pathname is formed by concatenating
+ the user's home directory, a "/", this partial pathname, another "/",
+ and the string ".k5login".
+ 
+ .IP k5login_required
+ This relation identifies whether a .k5login file must be found in
+ order for authorization to succeed. If absent, or if set to the value
+ 0, a .k5login file is not required if a user wishes to access a local
+ account whose name matches the principal part of his Kerberos
+ principal. If present with a non-zero value, a .k5login file is
+ required, e.g., it would need to list a user's Kerberos principal
+ even if he wished only to allow himself to login to his own account.
  .SH LOGIN SECTION
  The [login] section is used to configure the behavior of the Kerberos V5
  login program,


*** krb5-beta7/doc/krb5-user.info.old	Tue Sep 10 20:54:39 1996
--- krb5-beta7/doc/krb5-user.info	Tue Nov  5 21:13:04 1996
***************
*** 594,601 ****
  
  If you need to give someone access to log into your account, you can do
  so through Kerberos, without telling the person your password.  Simply
! create a file called `.k5login' in your home directory.  This file
! should contain the Kerberos principal (*Note What is a Kerberos
  Principal?::) of each person to whom you wish to give access.  Each
  principal must be on a separate line.  Here is a sample `.k5login' file:
  
--- 594,603 ----
  
  If you need to give someone access to log into your account, you can do
  so through Kerberos, without telling the person your password.  Simply
! create a file called `.k5login' in your home directory.  (It's
! possible that a directory other than your home directory will need to
! be used instead, depending on the configuration of your system.)  This
! file should contain the Kerberos principal (*Note What is a Kerberos
  Principal?::) of each person to whom you wish to give access.  Each
  principal must be on a separate line.  Here is a sample `.k5login' file:
  

*** krb5-beta7/src/include/k5-int.h.old	Fri Sep  6 18:20:49 1996
--- krb5-beta7/src/include/k5-int.h	Tue Nov  5 21:11:07 1996
***************
*** 932,937 ****
--- 932,939 ----
  	krb5_boolean	profile_secure;
  	int		fcc_default_format;
  	int		scc_default_format;
+ 	char	      FAR *k5login_location;
+ 	int		k5login_required;
  };
  
  #define KRB5_LIBOPT_SYNC_KDCTIME	0x0001


*** krb5-beta7/src/lib/krb5/krb/init_ctx.c.old	Wed Jul 10 20:32:12 1996
--- krb5-beta7/src/lib/krb5/krb/init_ctx.c	Tue Nov  5 22:46:34 1996
***************
*** 37,42 ****
--- 37,43 ----
  	krb5_context ctx;
  	krb5_error_code retval;
  	int tmp;
+ 	char *sretval;
  
  #if (defined(_MSDOS) || defined(_WIN32))
  	/*
***************
*** 69,78 ****
  			    0, 5 * 60, &tmp);
  	ctx->clockskew = tmp;
  
! 	/* Default ticket lifetime is 
  	profile_get_integer(ctx->profile, "libdefaults", "tkt_lifetime",
  			    0, 10 * 60 * 60, &tmp);
! 	ctx->clockskew = tmp;
  
  	/* DCE 1.1 and below only support CKSUMTYPE_RSA_MD4 (2)  */
  	/* DCE add kdc_req_checksum_type = 2 to krb5.conf */
--- 70,81 ----
  			    0, 5 * 60, &tmp);
  	ctx->clockskew = tmp;
  
! #if 0
! 	/* Default ticket lifetime is currently not supported */
  	profile_get_integer(ctx->profile, "libdefaults", "tkt_lifetime",
  			    0, 10 * 60 * 60, &tmp);
! 	ctx->tkt_lifetime = tmp;
! #endif
  
  	/* DCE 1.1 and below only support CKSUMTYPE_RSA_MD4 (2)  */
  	/* DCE add kdc_req_checksum_type = 2 to krb5.conf */
***************
*** 123,128 ****
--- 126,149 ----
  	ctx->fcc_default_format = tmp + 0x0500;
  	ctx->scc_default_format = tmp + 0x0500;
  
+ 	retval = profile_get_string(ctx->profile,
+ 				  "libdefaults", "k5login_location", NULL,
+ 				  "",
+ 				  &sretval);
+ 	if (retval)
+ 	    goto cleanup;
+ 
+ 	ctx->k5login_location = malloc(strlen(sretval) + 1);
+ 	if (!ctx->k5login_location) {
+ 		retval = ENOMEM;
+ 		goto cleanup;
+ 	}
+ 	strcpy(ctx->k5login_location, sretval);
+ 
+ 	profile_get_integer(ctx->profile, "libdefaults", "k5login_required",
+ 			    0, 0, &tmp);
+ 	ctx->k5login_required = tmp;
+ 
  	*context = ctx;
  	return 0;
  
***************
*** 148,153 ****
--- 169,177 ----
  
       if (ctx->ser_ctx_count && ctx->ser_ctx)
  	 free(ctx->ser_ctx);
+ 
+      if (ctx->k5login_location)
+ 	 free(ctx->k5login_location);
  
       ctx->magic = 0;
       free(ctx);


*** krb5-beta7/src/lib/krb5/krb/ser_ctx.c.old	Tue May 14 04:41:32 1996
--- krb5-beta7/src/lib/krb5/krb/ser_ctx.c	Tue Nov  5 19:26:08 1996
***************
*** 105,110 ****
--- 105,112 ----
       *	krb5_int32			for KV5M_CONTEXT
       *	krb5_int32			for sizeof(default_realm)
       *	strlen(default_realm)		for default_realm.
+      *	krb5_int32			for sizeof(k5login_location)
+      *	strlen(k5login_location)	for k5login_location.
       *	krb5_int32			for n_in_tkt_ktypes*sizeof(krb5_int32)
       *	nktypes*sizeof(krb5_int32)	for in_tkt_ktypes.
       *	krb5_int32			for n_tgs_ktypes*sizeof(krb5_int32)
***************
*** 118,123 ****
--- 120,126 ----
       *  krb5_int32			for profile_secure
       * 	krb5_int32			for fcc_default_format
       *  krb5_int32			for scc_default_format
+      *  krb5_int32			for k5login_required
       *    <>				for os_context
       *    <>				for db_context
       *    <>				for profile
***************
*** 126,137 ****
      kret = EINVAL;
      if ((context = (krb5_context) arg)) {
  	/* Calculate base length */
! 	required = (14 * sizeof(krb5_int32) +
  		    (context->in_tkt_ktype_count * sizeof(krb5_int32)) +
  		    (context->tgs_ktype_count * sizeof(krb5_int32)));
  
  	if (context->default_realm)
  	    required += strlen(context->default_realm);
  	/* Calculate size required by os_context, if appropriate */
  	if (context->os_context)
  	    kret = krb5_size_opaque(kcontext,
--- 129,142 ----
      kret = EINVAL;
      if ((context = (krb5_context) arg)) {
  	/* Calculate base length */
! 	required = (16 * sizeof(krb5_int32) +
  		    (context->in_tkt_ktype_count * sizeof(krb5_int32)) +
  		    (context->tgs_ktype_count * sizeof(krb5_int32)));
  
  	if (context->default_realm)
  	    required += strlen(context->default_realm);
+ 	if (context->k5login_location)
+ 	    required += strlen(context->k5login_location);
  	/* Calculate size required by os_context, if appropriate */
  	if (context->os_context)
  	    kret = krb5_size_opaque(kcontext,
***************
*** 210,215 ****
--- 215,236 ----
  	    return (kret);
      }
  
+     /* Now sizeof k5login location */
+     kret = krb5_ser_pack_int32((context->k5login_location) ?
+ 			       (krb5_int32) strlen(context->k5login_location) : 0,
+ 			       &bp, &remain);
+     if (kret)
+ 	return (kret);
+     
+     /* Now k5login_location bytes */
+     if (context->k5login_location) {
+ 	kret = krb5_ser_pack_bytes((krb5_octet *) context->k5login_location,
+ 				   strlen(context->k5login_location), 
+ 				   &bp, &remain);
+ 	if (kret)
+ 	    return (kret);
+     }
+ 
      /* Now number of initial ticket ktypes */
      kret = krb5_ser_pack_int32((krb5_int32) context->in_tkt_ktype_count,
  			       &bp, &remain);
***************
*** 292,297 ****
--- 313,324 ----
      if (kret)
  	return (kret);
  
+     /* Now k5login_required */
+     kret = krb5_ser_pack_int32((krb5_int32) context->k5login_required,
+ 			       &bp, &remain);
+     if (kret)
+ 	return (kret);
+ 
      /* Now handle os_context, if appropriate */
      if (context->os_context) {
  	kret = krb5_externalize_opaque(kcontext, KV5M_OS_CONTEXT,
***************
*** 384,390 ****
--- 411,436 ----
  	
  	context->default_realm[ibuf] = '\0';
      }
+ 
+     /* Get the size of the k5login location */
+     if ((kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain)))
+ 	goto cleanup;
+ 
+     if (ibuf) {
+ 	context->k5login_location = (char *) malloc((size_t) ibuf+1);
+ 	if (!context->k5login_location) {
+ 	    kret = ENOMEM;
+ 	    goto cleanup;
+ 	}
+ 
+ 	kret = krb5_ser_unpack_bytes((krb5_octet *) context->k5login_location,
+ 				     (size_t) ibuf, &bp, &remain);
+ 	if (kret)
+ 	    goto cleanup;
  	
+ 	context->k5login_location[ibuf] = '\0';
+     }
+ 	
      /* Get the number of in_tkt_ktypes */
      if ((kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain)))
  	goto cleanup;
***************
*** 468,473 ****
--- 514,524 ----
      if ((kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain)))
  	goto cleanup;
      context->scc_default_format = (int) ibuf;
+ 
+     /* k5login_required */
+     if ((kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain)))
+ 	goto cleanup;
+     context->k5login_required = (int) ibuf;
      
      /* Attempt to read in the os_context */
      kret = krb5_internalize_opaque(kcontext, KV5M_OS_CONTEXT,


*** krb5-beta7/src/lib/krb5/os/kuserok.c.old	Wed Jun 12 01:15:02 1996
--- krb5-beta7/src/lib/krb5/os/kuserok.c	Tue Nov  5 21:17:26 1996
***************
*** 77,92 ****
      if ((pwd = getpwnam(luser)) == NULL) {
  	return(FALSE);
      }
-     (void) strcpy(pbuf, pwd->pw_dir);
-     (void) strcat(pbuf, "/.k5login");
  
      if (access(pbuf, F_OK)) {	 /* not accessible */
  	/*
  	 * if he's trying to log in as himself, and there is no .k5login file,
! 	 * let him.  To find out, call
! 	 * krb5_aname_to_localname to convert the principal to a name
! 	 * which we can string compare. 
  	 */
  	if (!(krb5_aname_to_localname(context, principal,
  				      sizeof(kuser), kuser))
  	    && (strcmp(kuser, luser) == 0)) {
--- 77,115 ----
      if ((pwd = getpwnam(luser)) == NULL) {
  	return(FALSE);
      }
  
+     /*
+      * .k5login file may be in the top level of his home directory, in some
+      * subdirectory of his home directory, or in some other directory that
+      * has his username as one component of the pathname.
+      */
+     if (!context->k5login_location || context->k5login_location[0] == '\0') {
+         (void) strcpy(pbuf, pwd->pw_dir);
+         (void) strcat(pbuf, "/.k5login");
+     }
+     else if (context->k5login_location[0] != '/') {
+         (void) strcpy(pbuf, pwd->pw_dir);
+         (void) strcat(pbuf, "/");
+         (void) strcat(pbuf, context->k5login_location);
+         (void) strcat(pbuf, "/.k5login");
+     }
+     else {
+         (void) strcpy(pbuf, context->k5login_location);
+         (void) strcat(pbuf, "/");
+         (void) strcat(pbuf, pwd->pw_name);
+         (void) strcat(pbuf, "/.k5login");
+     }
+ 
      if (access(pbuf, F_OK)) {	 /* not accessible */
  	/*
  	 * if he's trying to log in as himself, and there is no .k5login file,
! 	 * let him, unless k5login_required is set.  To find out if he is
! 	 * logging in as himself, call krb5_aname_to_localname to convert the
! 	 * principal to a name which we can string compare.
  	 */
+ 	if (context->k5login_required)
+ 	    return(FALSE);
+       
  	if (!(krb5_aname_to_localname(context, principal,
  				      sizeof(kuser), kuser))
  	    && (strcmp(kuser, luser) == 0)) {
***************
*** 96,102 ****
      if (krb5_unparse_name(context, principal, &princname))
  	return(FALSE);			/* no hope of matching */
  
!     /* open ~/.k5login */
      if ((fp = fopen(pbuf, "r")) == NULL) {
  	free(princname);
  	return(FALSE);
--- 119,125 ----
      if (krb5_unparse_name(context, principal, &princname))
  	return(FALSE);			/* no hope of matching */
  
!     /* open the .k5login file */
      if ((fp = fopen(pbuf, "r")) == NULL) {
  	free(princname);
  	return(FALSE);

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