|
Vulnerability patchadd Affected SunOS Description Jonathan Fortin found following. He was playing around with patchadd and the bug was found when he issued a truss -f -o patch.log patchadd patch where patch was a tarball and then patchadd omitted an error because of it being a tarball, so then when Jonathan went through the debug output, he found out that there was a serious race condition vulnerability. Line Pid exec call 105: 12869: open64("/tmp/sh12869.1", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3 136: 12869: open64("/tmp/sh12869.2", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3 481: 12869: open64("/tmp/sh12869.3", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3 file "/tmp/sh12869.1": 105: 12869: open64("/tmp/sh12869.1", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3 106: 12869: write(3, "\n U s a g e : p a t c".., 482) = 482 107: 12869: close(3) file "/tmp/sh12869.2": 136: 12869: open64("/tmp/sh12869.2", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3 137: 12869: write(3, " m a i l =\n i n s t a n".., 145) = 145 138: 12869: close(3) file "/tmp/sh12869.3: 481: 12869: open64("/tmp/sh12869.3", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3 482: 12869: close(61) Err#9 EBADF 483: 12869: fcntl(3, F_DUPFD, 0x0000003D) = 61 484: 12869: close(3) When patchadd is executed, It creates a temporary file called "/tmp/sh<pidofpatchadd>.1" , "/tmp/sh<pidofpatchadd>.2 , "/tmp/sh<pidofpatchadd>.3 and assigns them mode 666 then gets unlink'd upon exit.A vulnerability exist in patchadd, a patch utility shipped with Solaris, where as if an attacker predicts the correct pid of the next process before execution of patchadd by another user or If he creates a fiew hundred symlinks to brute force the pid before execution of patchadd, he can with a symbolic link pointing to a specific key system file, overwrite contents of the file, he can do up to 3 file simultaneously, and user will be able to do his own modifications to this file since this file would have world-write permissions resulting in a increase of privilege and host compromise. Exploit: 1. Email admin telling him theirs a new patch out there that needs to be installed. 2. Create a perl/C script that will copy /etc/passwd and /etc/shadow to a hidden file that you will want to be appended to /etc/shadow/passwd later on, get the next current available process, create 2 symlinks and when the current process id is taken, then stat for /etc/passwd and /etc/shadow to be 666, if not avail, do it again, when avail, append a user with id 0 no password to those hidden files, then those files will truncate /etc/passwd and /etc/shadow then will be appended to them and send ya an email to login and take advantage! 3. su trojand_user 4. # Solution Sun Microsystems does recommend to only install patches at single-user mode (runlevel S). So no other possibly malicious user can exploit this ksh behaviour. Since patchadd is a script the bug it pretty easy to fix, Sun does intend to release patches. So here is a set of diffs to patchadd for those that really can't wait. Please note these are personal diffs and they do not reflect the official opinion of those in Sun responsible for the patchadd command and may not reflect the fix that is eventually released. *** patchadd.orig Wed Dec 20 11:21:38 2000 --- patchadd Wed Dec 20 11:21:42 2000 *************** *** 161,182 **** function set_globals { ! EXISTFILES=/tmp/existfiles.$$ ! PATCHFILES=/tmp/patchfiles.$$ ! PKGCOFILE=/tmp/pkgchk.out.$$ ! VALERRFILE=/tmp/valerr.$$ ! VALWARNFILE=/tmp/valwarn.$$ ! ADMINTFILE=/tmp/admin.tmp.$$ ! ADMINFILE=/tmp/admin.$$ ! LOGFILE=/tmp/pkgaddlog.$$ ! TMP_ARCHIVE=/tmp/TmpArchive.$$ ! TMP_FILELIST=/tmp/FileList.$$ ! TMP_LIB_DIR=/tmp/TmpLibDir.$$ ! INSTPATCHES_FILE=/tmp/MyShowrevFile.$$ ! PARAMS_FILE=/tmp/ParamsFile.$$ ! RESPONSE_FILE=/tmp/response.$$ ! TEMP_REMOTE=/tmp/temp_remote.$$ Obsoletes= Incompat= Requires= --- 161,192 ---- function set_globals { ! if [ -z "$WORKDIR" ]; then ! safedir=${TMPDIR:-/tmp}/patchadd${RANDOM}$$ ! mkdir -m 700 $safedir ! if [ $? != 0 ]; then ! exit 1; ! fi ! WORKDIR=$safedir ! unset safedir ! fi + EXISTFILES=${WORKDIR}/existfiles.$$ + PATCHFILES=${WORKDIR}/patchfiles.$$ + PKGCOFILE=${WORKDIR}/pkgchk.out.$$ + VALERRFILE=${WORKDIR}/valerr.$$ + VALWARNFILE=${WORKDIR}/valwarn.$$ + ADMINTFILE=${WORKDIR}/admin.tmp.$$ + ADMINFILE=${WORKDIR}/admin.$$ + LOGFILE=${WORKDIR}/pkgaddlog.$$ + TMP_ARCHIVE=${WORKDIR}/TmpArchive.$$ + TMP_FILELIST=${WORKDIR}/FileList.$$ + TMP_LIB_DIR=${WORKDIR}/TmpLibDir.$$ + INSTPATCHES_FILE=${WORKDIR}/MyShowrevFile.$$ + PARAMS_FILE=${WORKDIR}/ParamsFile.$$ + RESPONSE_FILE=${WORKDIR}/response.$$ + TEMP_REMOTE=${WORKDIR}/temp_remote.$$ + Obsoletes= Incompat= Requires= *************** *** 305,314 **** fi $RM -f $INSTPATCHES_FILE - $RM -f /tmp/*.$$.1 - $RM -f /tmp/archive.cpio* - $RM -fr /tmp/*.$$ $RM -f $patchFileStripped } # --- 315,322 ---- fi $RM -f $INSTPATCHES_FILE $RM -f $patchFileStripped + $RM -rf ${WORKDIR} } # *************** *** 435,441 **** /usr/bin/gettext "The postpatch script exited with return code $retcode.\n" if [[ "$isapplied" = "no" ]] then ! $CP $1/$2/log /tmp/log.$2 /usr/bin/gettext "Backing out patch:\n" cd $3 if [[ "$ROOTDIR" != "/" ]] --- 443,449 ---- /usr/bin/gettext "The postpatch script exited with return code $retcode.\n" if [[ "$isapplied" = "no" ]] then ! $CP $1/$2/log ${WORKDIR}/log.$2 /usr/bin/gettext "Backing out patch:\n" cd $3 if [[ "$ROOTDIR" != "/" ]] *************** *** 444,450 **** else /usr/sbin/patchrm $2 fi ! /usr/bin/gettext "See /tmp/log.$2 for more details.\n" else /usr/bin/gettext "Not backing out patch because this is a re-installation.\nThe system may be in an unstable state!\nSee $1/$2/log for more details.\n" |tee -a $1/$2/log fi --- 452,458 ---- else /usr/sbin/patchrm $2 fi ! /usr/bin/gettext "See ${WORKDIR}/log.$2 for more details.\n" else /usr/bin/gettext "Not backing out patch because this is a re-installation.\nThe system may be in an unstable state!\nSee $1/$2/log for more details.\n" |tee -a $1/$2/log fi *************** *** 1527,1533 **** # function check_for_symbolic_link { ! $RM -f /tmp/symlink.$$ > /dev/null 2>&1 olddir=$(pwd) cd $patchdir for ii in * X --- 1535,1541 ---- # function check_for_symbolic_link { ! $RM -f ${WORKDIR}/symlink.$$ > /dev/null 2>&1 olddir=$(pwd) cd $patchdir for ii in * X *************** *** 1551,1562 **** symlinks= symlinks=$($SED -n '/^[^ ]*[ ]*s[ ]/p' $1/$2/$ii/pkgmap) if [[ "$symlinks" != "" ]]; then ! /usr/bin/gettext "Symbolic link in package $ii.\n" >> /tmp/symlink.$$ fi done ! if [[ -s /tmp/symlink.$$ ]] then ! cat /tmp/symlink.$$ /usr/bin/gettext "Symbolic links cannot be part of a patch.\n" patch_quit 13 "no" return 0 --- 1559,1570 ---- symlinks= symlinks=$($SED -n '/^[^ ]*[ ]*s[ ]/p' $1/$2/$ii/pkgmap) if [[ "$symlinks" != "" ]]; then ! /usr/bin/gettext "Symbolic link in package $ii.\n" >> ${WORKDIR}/symlink.$$ fi done ! if [[ -s ${WORKDIR}/symlink.$$ ]] then ! cat ${WORKDIR}/symlink.$$ /usr/bin/gettext "Symbolic links cannot be part of a patch.\n" patch_quit 13 "no" return 0 *************** *** 1840,1848 **** return fi ! pkgfiles=/tmp/pkgfiles.$$ ! resfiles=/tmp/resolvedfiles.$$ ! macrofiles=/tmp/pkgmacros.$$ pkginst= pkginfofile= patchpkg= --- 1848,1856 ---- return fi ! pkgfiles=${WORKDIR}/pkgfiles.$$ ! resfiles=${WORKDIR}/resolvedfiles.$$ ! macrofiles=${WORKDIR}/pkgmacros.$$ pkginst= pkginfofile= patchpkg= *************** *** 2343,2350 **** /usr/bin/gettext "Pkgadd of $i package failed with error code $pkgadderr.\n" |tee -a $1/$2/log if [ "$isapplied" = "no" ] then ! /usr/bin/gettext "See /tmp/log.$2 for reason for failure.\n" ! $CP $1/$2/log /tmp/log.$2 /usr/bin/gettext "Backing out patch:\n" cd $3 if [ "$ROOTDIR" != "/" ] --- 2351,2358 ---- /usr/bin/gettext "Pkgadd of $i package failed with error code $pkgadderr.\n" |tee -a $1/$2/log if [ "$isapplied" = "no" ] then ! /usr/bin/gettext "See ${WORKDIR}/log.$2 for reason for failure.\n" ! $CP $1/$2/log ${WORKDIR}/log.$2 /usr/bin/gettext "Backing out patch:\n" cd $3 if [ "$ROOTDIR" != "/" ] *************** *** 2988,2998 **** $FIND . -print > $TMP_FILELIST cd $ROOTDIR ! cpio -oL -O /tmp/archive.cpio < $EXISTFILES >/dev/null 2>&1 exit_code=$? cd $TMP_ARCHIVE ! cpio -oAL -O /tmp/archive.cpio < $TMP_FILELIST >/dev/null 2>&1 exit_code=exit_code+$? cd $ROOTDIR --- 2996,3006 ---- $FIND . -print > $TMP_FILELIST cd $ROOTDIR ! cpio -oL -O ${WORKDIR}/archive.cpio < $EXISTFILES >/dev/null 2>&1 exit_code=$? cd $TMP_ARCHIVE ! cpio -oAL -O ${WORKDIR}/archive.cpio < $TMP_FILELIST >/dev/null 2>&1 exit_code=exit_code+$? cd $ROOTDIR *************** *** 3022,3028 **** then compress $archive_path/archive.cpio else ! compress /tmp/archive.cpio fi if [ $? = 0 ] then --- 3030,3036 ---- then compress $archive_path/archive.cpio else ! compress ${WORKDIR}/archive.cpio fi if [ $? = 0 ] then *************** *** 3035,3041 **** fi if [ "$isapplied" = "yes" ] then ! $CP /tmp/archive.cpio* $1/$2/save fi chmod 600 $archive_path/archive.cpio* $TOUCH $1/$2/.oldfilessaved --- 3043,3049 ---- fi if [ "$isapplied" = "yes" ] then ! $CP ${WORKDIR}/archive.cpio* $1/$2/save fi chmod 600 $archive_path/archive.cpio* $TOUCH $1/$2/.oldfilessaved *************** *** 3057,3063 **** then $MD -m 750 -p $1/$2 fi ! $MV -f /tmp/ACTION.$PatchNum $1/$2 >/dev/null 2>&1 $CP -p README.$2 $1/$2 >/dev/null 2>&1 # Note the following line should be removed for 2.7. --- 3065,3071 ---- then $MD -m 750 -p $1/$2 fi ! $MV -f ${WORKDIR}/ACTION.$PatchNum $1/$2 >/dev/null 2>&1 $CP -p README.$2 $1/$2 >/dev/null 2>&1 # Note the following line should be removed for 2.7. *************** *** 3092,3102 **** else if [[ "$PATCH_UNDO_ARCHIVE" != "none" ]] then ! $CP /tmp/archive.cpio* $PATCH_UNDO_ARCHIVE/$2 else ! $CP /tmp/archive.cpio* $1/$2/save fi ! $RM -f /tmp/archive.cpio* /usr/bin/gettext "Patchadd Interrupted.\n" >> $1/$2/log fi patch_quit 12 "yes" --- 3100,3110 ---- else if [[ "$PATCH_UNDO_ARCHIVE" != "none" ]] then ! $CP ${WORKDIR}/archive.cpio* $PATCH_UNDO_ARCHIVE/$2 else ! $CP ${WORKDIR}/archive.cpio* $1/$2/save fi ! $RM -f ${WORKDIR}/archive.cpio* /usr/bin/gettext "Patchadd Interrupted.\n" >> $1/$2/log fi patch_quit 12 "yes" *************** *** 3119,3125 **** fi if [[ "$isapplied" = "yes" ]] then ! $RM -f /tmp/archive.cpio* fi patch_quit 12 "yes" } --- 3127,3133 ---- fi if [[ "$isapplied" = "yes" ]] then ! $RM -f ${WORKDIR}/archive.cpio* fi patch_quit 12 "yes" } *************** *** 3132,3138 **** function trap_notinstalled { /usr/bin/gettext "Interrupt signal detected. Patch not installed.\n" ! $RM -fr /tmp/*.$$ $RM -f $INSTPATCHES_FILE if [[ "$isapplied" = "no" ]] then --- 3140,3146 ---- function trap_notinstalled { /usr/bin/gettext "Interrupt signal detected. Patch not installed.\n" ! $RM -fr ${WORKDIR}/*.$$ $RM -f $INSTPATCHES_FILE if [[ "$isapplied" = "no" ]] then *************** *** 3411,3417 **** typeset -i insPs=0 patchFile="" ! patchFileStripped=/tmp/patchDBstripped.$$ if [[ "$validate" = "no" ]] then --- 3419,3425 ---- typeset -i insPs=0 patchFile="" ! patchFileStripped=${WORKDIR}/patchDBstripped.$$ if [[ "$validate" = "no" ]] then *************** *** 3796,3802 **** else if [[ "$netImage" = "boot" ]] then ! backout_dir=$ROOTDIR/tmp else backout_dir=$PKGDB fi --- 3804,3810 ---- else if [[ "$netImage" = "boot" ]] then ! backout_dir=$ROOTDIR${WORKDIR} else backout_dir=$PKGDB fi *************** *** 3850,3856 **** dryrunExit= dryrunDir= ! dryrunDir="/tmp/$PatchNum.$$" if [[ ! -d "$dryrunDir" || "$1" != "0" ]]; then dryrunFailure="yes" --- 3858,3864 ---- dryrunExit= dryrunDir= ! dryrunDir="${WORKDIR}/$PatchNum.$$" if [[ ! -d "$dryrunDir" || "$1" != "0" ]]; then dryrunFailure="yes" *************** *** 3955,3967 **** restore_net_image fi ! # The .../Boot/.tmp_proto/root needs to be re-mapped to .../Boot/tmp in order # for the boot image to be patched successfully. if [[ -z "$RE_MINIROOT_PATCH" ]]; then ! $MOUNT -F lofs -O $ROOTDIR/tmp $ROOTDIR/mnt ! $MOUNT -F lofs -O $ROOTDIR/.tmp_proto $ROOTDIR/tmp ! $MOUNT -F lofs -O $ROOTDIR/mnt/root/var $ROOTDIR/tmp/root/var fi # At this point patchadd thinks the net install image is just like --- 3963,3975 ---- restore_net_image fi ! # The .../Boot/.tmp_proto/root needs to be re-mapped to .../Boot${WORKDIR} in order # for the boot image to be patched successfully. if [[ -z "$RE_MINIROOT_PATCH" ]]; then ! $MOUNT -F lofs -O $ROOTDIR${WORKDIR} $ROOTDIR/mnt ! $MOUNT -F lofs -O $ROOTDIR/.tmp_proto $ROOTDIR${WORKDIR} ! $MOUNT -F lofs -O $ROOTDIR/mnt/root/var $ROOTDIR${WORKDIR}/root/var fi # At this point patchadd thinks the net install image is just like *************** *** 3983,3990 **** fi if [[ -z "$RE_MINIROOT_PATCH" ]]; then ! $UMOUNT $ROOTDIR/tmp/root/var ! $UMOUNT $ROOTDIR/tmp $UMOUNT $ROOTDIR/mnt fi } --- 3991,3998 ---- fi if [[ -z "$RE_MINIROOT_PATCH" ]]; then ! $UMOUNT $ROOTDIR${WORKDIR}/root/var ! $UMOUNT $ROOTDIR${WORKDIR} $UMOUNT $ROOTDIR/mnt fi } *************** *** 4168,4176 **** if [[ $pkgadd_code == 5 ]] # administration then ! mv $LOGFILE /var/tmp/$PatchNum.log.$$ > /dev/null 2>&1 [ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var/tmp/$PatchNum.log.$$ for details\n" remove_patch_meta_data "$pkg" [ -n "$pkgsAlreadyInstalled" ] && remove_patch --- 4176,4184 ---- if [[ $pkgadd_code == 5 ]] # administration then ! mv $LOGFILE /var${WORKDIR}/$PatchNum.log.$$ > /dev/null 2>&1 [ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var${WORKDIR}/$PatchNum.log.$$ for details\n" remove_patch_meta_data "$pkg" [ -n "$pkgsAlreadyInstalled" ] && remove_patch *************** *** 4188,4196 **** fi elif [[ $pkgadd_code != 0 ]] then ! mv $LOGFILE /var/tmp/$PatchNum.log.$$ > /dev/null 2>&1 [ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var/tmp/$PatchNum.log.$$ for details\n" # If there are more pkgs in the list skip them # since this patch will not be installed. --- 4196,4204 ---- fi elif [[ $pkgadd_code != 0 ]] then ! mv $LOGFILE /var${WORKDIR}/$PatchNum.log.$$ > /dev/null 2>&1 [ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var${WORKDIR}/$PatchNum.log.$$ for details\n" # If there are more pkgs in the list skip them # since this patch will not be installed. *************** *** 4321,4331 **** if [[ "$firstTimeThru" = "yes" ]] then if [[ "$PKGADD_DEBUG" = "yes" ]]; then ! pkgadd -v -D /tmp/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist else ! pkgadd -D /tmp/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist 1>>$LOGFILE </dev/null 2>&1 --- 4329,4339 ---- if [[ "$firstTimeThru" = "yes" ]] then if [[ "$PKGADD_DEBUG" = "yes" ]]; then ! pkgadd -v -D ${WORKDIR}/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist else ! pkgadd -D ${WORKDIR}/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist 1>>$LOGFILE </dev/null 2>&1