-----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-----