Security advisory: krb5 ftpd buffer overflows

From: Tom Yu (tlyuat_private)
Date: Wed Apr 25 2001 - 17:51:48 PDT

  • Next message: Rich Lafferty: "Re: SECURITY.NNOV: The Bat! <cr> bug"

    -----BEGIN PGP SIGNED MESSAGE-----
    
    		      KRB5 FTPD BUFFER OVERFLOWS
    
    2001-04-25
    
    SUMMARY:
    
    Buffer overflows exist in the FTP daemon included with MIT krb5.
    
    IMPACT:
    
    * If anonymous FTP is enabled, a remote user may gain unauthorized
      root access.
    
    * A user with access to a local account may gain unauthorized root
      access.
    
    * A remote user who can successfully authenticate to the FTP daemon
      may obtain unauthorized root access, regardless of whether anonymous
      FTP is enabled or whether access is granted to a local account.
      This vulnerability is believed to be somewhat difficult to exploit.
    
    VULNERABLE DISTRIBUTIONS:
    
    * MIT Kerberos 5, all releases.
    
    FIXES:
    
    The recommended approach is to apply the included patches and to
    rebuild your ftpd.  The included patches are against krb5-1.2.2.
    
    If you cannot patch your ftpd currently, workarounds include disabling
    anonymous FTP access, if you have it enabled; this will limit the most
    likely exploitation to users with local account access or who can
    successfully authenticate to the daemon.
    
    This announcement and code patches related to it may be found on the
    MIT Kerberos security advisory page at:
    
    	http://web.mit.edu/kerberos/www/advisories/index.html
    
    The main MIT Kerberos web page is at:
    
    	http://web.mit.edu/kerberos/www/index.html
    
    ACKNOWLEDGEMENTS:
    
    Thanks to Matt Crawford for providing some insight into the specific
    ways in which krb5 ftpd is vulnerable.
    
    DETAILS:
    
    The remote vulnerability exploitable via anonymous FTP or local
    account access results from a buffer overflow in code that calls
    ftpglob(), a function responsible for expanding glob characters in
    pathnames.  Recent versions of ftpd (krb5-1.2 or later) should not
    contain buffer overflows in the ftpglob() function itself.
    
    Remote users able to authenticate to the FTP daemon may be able to
    exploit a lack of bounds-checking in calling radix_encode().  Login
    access is not required; the ability to force arbitrary data to be
    base64-encoded by radix_encode() is sufficient.
    
    This vulnerability is believed to be somewhat difficult to exploit
    (but by no means impossible) due to the need for an attacker to inject
    data that will base64-encode to the desired machine code and target
    address.
    
    PATCHES AGAINST krb5-1.2.2:
    
    These patches are against the krb5-1.2.2 release.  They may also apply
    against earlier releases, though.  The patches may also be found at:
    
    	http://web.mit.edu/kerberos/www/advisories/ftpbuf_122_patch.txt
    
    Index: ftpcmd.y
    ===================================================================
    RCS file: /cvs/krbdev/krb5/src/appl/gssftp/ftpd/ftpcmd.y,v
    retrieving revision 1.14.4.2
    diff -c -r1.14.4.2 ftpcmd.y
    *** ftpcmd.y	2001/01/17 23:25:16	1.14.4.2
    - --- ftpcmd.y	2001/04/25 20:16:45
    ***************
    *** 805,815 ****
      		 * This is a valid reply in some cases but not in others.
      		 */
      		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
    ! 			*(char **)&($$) = *ftpglob((char *) $1);
    ! 			if (globerr != NULL) {
      				reply(550, globerr);
      				$$ = NULL;
    ! 			}
      			free((char *) $1);
      		} else
      			$$ = $1;
    - --- 805,819 ----
      		 * This is a valid reply in some cases but not in others.
      		 */
      		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
    ! 			char **vv;
    !
    ! 			vv = ftpglob((char *) $1);
    ! 			if (vv == NULL || globerr != NULL) {
      				reply(550, globerr);
      				$$ = NULL;
    ! 			} else
    ! 				$$ = *vv;
    !
      			free((char *) $1);
      		} else
      			$$ = $1;
    Index: ftpd.c
    ===================================================================
    RCS file: /cvs/krbdev/krb5/src/appl/gssftp/ftpd/ftpd.c,v
    retrieving revision 1.43.2.1
    diff -c -r1.43.2.1 ftpd.c
    *** ftpd.c	2000/05/23 21:39:07	1.43.2.1
    - --- ftpd.c	2001/04/25 20:16:48
    ***************
    *** 761,767 ****
    - --- 761,777 ----
      		int result;
      #ifdef GSSAPI
      		if (auth_type && strcmp(auth_type, "GSSAPI") == 0) {
    + 			int len;
    +
      			authorized = ftpd_gss_userok(&client_name, name) == 0;
    + 			len = sizeof("GSSAPI user  is not authorized as "
    + 				     "; Password required.")
    + 				+ strlen(client_name.value)
    + 				+ strlen(name);
    + 			if (len >= sizeof(buf)) {
    + 				syslog(LOG_ERR, "user: username too long");
    + 				name = "[username too long]";
    + 			}
      			sprintf(buf, "GSSAPI user %s is%s authorized as %s",
      				client_name.value, authorized ? "" : " not",
      				name);
    ***************
    *** 772,778 ****
    - --- 782,800 ----
      #endif /* GSSAPI */
      #ifdef KRB5_KRB4_COMPAT
      		if (auth_type && strcmp(auth_type, "KERBEROS_V4") == 0) {
    + 			int len;
    +
      			authorized = kuserok(&kdata,name) == 0;
    + 			len = sizeof("Kerberos user .@ is not authorized as "
    + 				     "; Password required.")
    + 				+ strlen(kdata.pname)
    + 				+ strlen(kdata.pinst)
    + 				+ strlen(kdata.prealm)
    + 				+ strlen(name);
    + 			if (len >= sizeof(buf)) {
    + 				syslog(LOG_ERR, "user: username too long");
    + 				name = "[username too long]";
    + 			}
      			sprintf(buf, "Kerberos user %s%s%s@%s is%s authorized as %s",
      				kdata.pname, *kdata.pinst ? "." : "",
      				kdata.pinst, kdata.prealm,
    ***************
    *** 1179,1184 ****
    - --- 1201,1211 ----
      	} else {
      		char line[FTP_BUFSIZ];
    
    + 		if (strlen(cmd) + strlen(name) + 1 >= sizeof(line)) {
    + 			syslog(LOG_ERR, "retrieve: filename too long");
    + 			reply(501, "filename too long");
    + 			return;
    + 		}
      		(void) sprintf(line, cmd, name), name = line;
      		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
      		st.st_size = -1;
    ***************
    *** 1417,1422 ****
    - --- 1444,1453 ----
      	return (file);
      }
    
    + /*
    +  * XXX callers need to limit total length of output string to
    +  * FTP_BUFSIZ
    +  */
      #ifdef STDARG
      secure_error(char *fmt, ...)
      #else
    ***************
    *** 1616,1628 ****
      {
      	char line[FTP_BUFSIZ];
      	FILE *fin;
    ! 	int c;
      	char str[FTP_BUFSIZ], *p;
    
      	(void) sprintf(line, "/bin/ls -lgA %s", filename);
      	fin = ftpd_popen(line, "r");
      	lreply(211, "status of %s:", filename);
      	p = str;
      	while ((c = getc(fin)) != EOF) {
      		if (c == '\n') {
      			if (ferror(stdout)){
    - --- 1647,1665 ----
      {
      	char line[FTP_BUFSIZ];
      	FILE *fin;
    ! 	int c, n;
      	char str[FTP_BUFSIZ], *p;
    
    + 	if (strlen(filename) + sizeof("/bin/ls -lgA ")
    + 	    >= sizeof(line)) {
    + 		reply(501, "filename too long");
    + 		return;
    + 	}
      	(void) sprintf(line, "/bin/ls -lgA %s", filename);
      	fin = ftpd_popen(line, "r");
      	lreply(211, "status of %s:", filename);
      	p = str;
    + 	n = 0;
      	while ((c = getc(fin)) != EOF) {
      		if (c == '\n') {
      			if (ferror(stdout)){
    ***************
    *** 1639,1645 ****
      			*p = '\0';
      			reply(0, "%s", str);
      			p = str;
    ! 		} else	*p++ = c;
      	}
      	if (p != str) {
      		*p = '\0';
    - --- 1676,1691 ----
      			*p = '\0';
      			reply(0, "%s", str);
      			p = str;
    ! 			n = 0;
    ! 		} else {
    ! 			*p++ = c;
    ! 			n++;
    ! 			if (n >= sizeof(str)) {
    ! 				reply(551, "output line too long");
    ! 				(void) ftpd_pclose(fin);
    ! 				return;
    ! 			}
    ! 		}
      	}
      	if (p != str) {
      		*p = '\0';
    ***************
    *** 1723,1728 ****
    - --- 1769,1778 ----
    
      char cont_char = ' ';
    
    + /*
    +  * XXX callers need to limit total length of output string to
    +  * FTP_BUFSIZ bytes for now.
    +  */
      #ifdef STDARG
      reply(int n, char *fmt, ...)
      #else
    ***************
    *** 1744,1765 ****
      #endif
    
      	if (auth_type) {
    ! 		char in[FTP_BUFSIZ], out[FTP_BUFSIZ];
      		int length, kerror;
      		if (n) sprintf(in, "%d%c", n, cont_char);
      		else in[0] = '\0';
      		strncat(in, buf, sizeof (in) - strlen(in) - 1);
      #ifdef KRB5_KRB4_COMPAT
      		if (strcmp(auth_type, "KERBEROS_V4") == 0) {
    ! 			if ((length = clevel == PROT_P ?
    ! 			     krb_mk_priv((unsigned char *)in,
    ! 					 (unsigned char *)out,
    ! 					 strlen(in), schedule, &kdata.session,
    ! 					 &ctrl_addr, &his_addr)
    ! 			     : krb_mk_safe((unsigned char *)in,
    ! 					   (unsigned char *)out,
    ! 					   strlen(in), &kdata.session,
    ! 					   &ctrl_addr, &his_addr)) == -1) {
      				syslog(LOG_ERR,
      				       "krb_mk_%s failed for KERBEROS_V4",
      				       clevel == PROT_P ? "priv" : "safe");
    - --- 1794,1825 ----
      #endif
    
      	if (auth_type) {
    ! 		/*
    ! 		 * Deal with expansion in mk_{safe,priv},
    ! 		 * radix_encode, gss_seal, plus slop.
    ! 		 */
    ! 		char in[FTP_BUFSIZ*3/2], out[FTP_BUFSIZ*3/2];
      		int length, kerror;
      		if (n) sprintf(in, "%d%c", n, cont_char);
      		else in[0] = '\0';
      		strncat(in, buf, sizeof (in) - strlen(in) - 1);
      #ifdef KRB5_KRB4_COMPAT
      		if (strcmp(auth_type, "KERBEROS_V4") == 0) {
    ! 			if (clevel == PROT_P)
    ! 				length = krb_mk_priv((unsigned char *)in,
    ! 						     (unsigned char *)out,
    ! 						     strlen(in),
    ! 						     schedule, &kdata.session,
    ! 						     &ctrl_addr,
    ! 						     &his_addr);
    ! 			else
    ! 				length = krb_mk_safe((unsigned char *)in,
    ! 						     (unsigned char *)out,
    ! 						     strlen(in),
    ! 						     &kdata.session,
    ! 						     &ctrl_addr,
    ! 						     &his_addr);
    ! 			if (length == -1) {
      				syslog(LOG_ERR,
      				       "krb_mk_%s failed for KERBEROS_V4",
      				       clevel == PROT_P ? "priv" : "safe");
    ***************
    *** 1803,1815 ****
      		}
      #endif /* GSSAPI */
      		/* Other auth types go here ... */
    ! 		if (kerror = radix_encode(out, in, &length, 0)) {
      			syslog(LOG_ERR, "Couldn't encode reply (%s)",
      					radix_error(kerror));
      			fputs(in,stdout);
      		} else
    ! 		printf("%s%c%s", clevel == PROT_P ? "632" : "631",
    ! 				 n ? cont_char : '-', in);
      	} else {
      		if (n) printf("%d%c", n, cont_char);
      		fputs(buf, stdout);
    - --- 1863,1878 ----
      		}
      #endif /* GSSAPI */
      		/* Other auth types go here ... */
    ! 		if (length >= sizeof(in) / 4 * 3) {
    ! 			syslog(LOG_ERR, "input to radix_encode too long");
    ! 			fputs(in, stdout);
    ! 		} else if (kerror = radix_encode(out, in, &length, 0)) {
      			syslog(LOG_ERR, "Couldn't encode reply (%s)",
      					radix_error(kerror));
      			fputs(in,stdout);
      		} else
    ! 			printf("%s%c%s", clevel == PROT_P ? "632" : "631",
    ! 			       n ? cont_char : '-', in);
      	} else {
      		if (n) printf("%d%c", n, cont_char);
      		fputs(buf, stdout);
    ***************
    *** 1822,1827 ****
    - --- 1885,1894 ----
      	}
      }
    
    + /*
    +  * XXX callers need to limit total length of output string to
    +  * FTP_BUFSIZ
    +  */
      #ifdef STDARG
      lreply(int n, char *fmt, ...)
      #else
    ***************
    *** 1866,1872 ****
    
      	if (cp = strchr(cbuf,'\n'))
      		*cp = '\0';
    ! 	reply(500, "'%s': command not understood.", cbuf);
      }
    
      delete_file(name)
    - --- 1933,1940 ----
    
      	if (cp = strchr(cbuf,'\n'))
      		*cp = '\0';
    ! 	reply(500, "'%.*s': command not understood.",
    ! 	      FTP_BUFSIZ - sizeof("'': command not understood."), cbuf);
      }
    
      delete_file(name)
    ***************
    *** 2143,2149 ****
      	int code;
      	char *string;
      {
    ! 	reply(code, "%s: %s.", string, strerror(errno));
      }
    
      auth(type)
    - --- 2211,2233 ----
      	int code;
      	char *string;
      {
    ! 	char *err_string;
    ! 	size_t extra_len;
    !
    ! 	err_string = strerror(errno);
    ! 	if (err_string == NULL)
    ! 		err_string = "(unknown error)";
    ! 	extra_len = strlen(err_string) + sizeof("(truncated): .");
    !
    ! 	/*
    ! 	 * XXX knows about FTP_BUFSIZ in reply()
    ! 	 */
    ! 	if (strlen(string) + extra_len > FTP_BUFSIZ) {
    ! 		reply(code, "(truncated)%.*s: %s.",
    ! 		      FTP_BUFSIZ - extra_len, string, err_string);
    ! 	} else {
    ! 		reply(code, "%s: %s.", string, err_string);
    ! 	}
      }
    
      auth(type)
    ***************
    *** 2226,2231 ****
    - --- 2310,2319 ----
      			secure_error("ADAT: krb_mk_safe failed");
      			return(0);
      		}
    + 		if (length >= (FTP_BUFSIZ - sizeof("ADAT=")) / 4 * 3) {
    + 			secure_error("ADAT: reply too long");
    + 			return(0);
    + 		}
      		if (kerror = radix_encode(out_buf, buf, &length, 0)) {
      			secure_error("Couldn't encode ADAT reply (%s)",
      				     radix_error(kerror));
    ***************
    *** 2360,2365 ****
    - --- 2448,2463 ----
      		}
    
      		if (out_tok.length) {
    + 			if (out_tok.length >= ((FTP_BUFSIZ - sizeof("ADAT="))
    + 					       / 4 * 3)) {
    + 				secure_error("ADAT: reply too long");
    + 				syslog(LOG_ERR, "ADAT: reply too long");
    + 				(void) gss_release_cred(&stat_min, &server_creds);
    + 				if (ret_flags & GSS_C_DELEG_FLAG)
    + 					(void) gss_release_cred(&stat_min,
    + 								&deleg_creds);
    + 				return(0);
    + 			}
      			if (kerror = radix_encode(out_tok.value, gbuf, &out_tok.length, 0)) {
      				secure_error("Couldn't encode ADAT reply (%s)",
      					     radix_error(kerror));
    ***************
    *** 2458,2463 ****
    - --- 2556,2564 ----
       *	n>=0 on success
       *	-1 on error
       *	-2 on security error
    +  *
    +  * XXX callers need to limit total length of output string to
    +  * FTP_BUFSIZ
       */
      #ifdef STDARG
      secure_fprintf(FILE *stream, char *fmt, ...)
    ***************
    *** 2575,2580 ****
    - --- 2676,2690 ----
      			    dir->d_name[2] == '\0')
      				continue;
    
    + 			if (strlen(dirname) + strlen(dir->d_name)
    + 			    + 1 /* slash */
    + 			    + 2	/* CRLF */
    + 			    + 1 > sizeof(nbuf)) {
    + 				syslog(LOG_ERR,
    + 				       "send_file_list: pathname too long");
    + 				ret = -2; /* XXX */
    + 				goto data_err;
    + 			}
      			sprintf(nbuf, "%s/%s", dirname, dir->d_name);
    
      			/*
    
    -----BEGIN PGP SIGNATURE-----
    Version: PGP 6.5.8
    
    iQCVAwUBOudtAKbDgE/zdoE9AQHhJgP/RFEDX/KL3YoavQSP9jJYO+GTg2MBfWRd
    B4wakx2PYbt4LSGSNu/VyZKFGQhVqe0F38C7oGBrCyRzZfC5MPSBmo/B6pxaeM9P
    oUo3Bny+JgybyOZ9wp7pGW2cRHH/zKbakrsaGFWgeAucceZeDana+TEZqGlQLIst
    wfRPsXU7WA8=
    =+0c0
    -----END PGP SIGNATURE-----
    



    This archive was generated by hypermail 2b30 : Wed Apr 25 2001 - 22:33:32 PDT