Security advisory: krb5 ftpd buffer overflows
Date: Wed, 25 Apr 2001 20:51:48 -0400
From: Tom Yu <[email protected]>
To: [email protected]
Subject: Security advisory: krb5 ftpd buffer overflows
-----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-----