Microsoft Corporation
Updated April 15, 1999
When using the Microsoft® FrontPage® 2000 Server Extensions for UNIX with the Apache Web server, the recommended configuration is to patch the Apache Web server. The patch to the Apache Web server consists of two modifications:
The source code to fpexe.c is presented here for review.
* ==================================================================== * * FrontPage SUID Stub Executable * * Copyright (c) 1995-1998 Microsoft Corporation -- All Rights Reserved. * * NO WARRANTIES. Microsoft expressly disclaims any warranty for this code and * information. This code and information and any related documentation is * provided "as is" without warranty of any kind, either express or implied, * including, without limitation, the implied warranties or merchantability, * fitness for a particular purpose, or noninfringement. The entire risk * arising out of use or performance of this code and information remains with * you. * * NO LIABILITY FOR DAMAGES. In no event shall Microsoft or its suppliers be * liable for any damages whatsoever (including, without limitation, damages * for loss of business profits, business interruption, loss of business * information, or any other pecuniary loss) arising out of the use of or * inability to use this Microsoft product, even if Microsoft has been advised * of the possibility of such damages. Because some states/jurisdictions do not * allow the exclusion or limitation of liability for consequential or * incidental damages, the above limitation may not apply to you. * * Version 4.0.4.3 */ /* * User configurable items. We will not run the server extensions with any * UID/GID less than LOWEST_VALID_UID/LOWEST_VALID_GID. */ #if defined(RS6000) #define _ALL_SOURCE #endif #if defined(RS6000) | defined(UWARE7) int initgroups (char *, int); #endif #if defined(LINUX) #define LOWEST_VALID_UID 15 #else #define LOWEST_VALID_UID 11 #endif #if defined(HPUX) || defined(IRIX) || defined(SUNOS4) #define LOWEST_VALID_GID 20 #else #if defined(SCO) #define LOWEST_VALID_GID 24 #else #define LOWEST_VALID_GID 21 /* Solaris, AIX, Alpha, Bsdi, etc. */ #endif #endif #if defined(UWARE7) #define Vstat stat32 #define Vlstat lstat32 int lstat32 (const char *, struct stat *); int stat32 (const char *, struct stat *); #else #define Vstat stat #define Vlstat lstat #endif #define CLEAN_PATH "PATH=/usr/bin:/bin" static struct SaveEnvVars { const char* szVar; int iLen; } gSafeEnvVars[] = { { "AUTH_TYPE=", 0 }, { "CONTENT_LENGTH=", 0 }, { "CONTENT_TYPE=", 0 }, { "DATE_GMT=", 0 }, { "DATE_LOCAL=", 0 }, { "DOCUMENT_NAME=", 0 }, { "DOCUMENT_PATH_INFO=", 0 }, { "DOCUMENT_ROOT=", 0 }, { "DOCUMENT_URI=", 0 }, { "FILEPATH_INFO=", 0 }, { "GATEWAY_INTERFACE=", 0 }, { "HTTP_", 0 }, { "LAST_MODIFIED=", 0 }, { "PATH_INFO=", 0 }, { "PATH_TRANSLATED=", 0 }, { "QUERY_STRING=", 0 }, { "QUERY_STRING_UNESCAPED=", 0 }, { "REDIRECT_QUERY_STRING=", 0 }, { "REDIRECT_STATUS=", 0 }, { "REDIRECT_URL=", 0 }, { "REMOTE_ADDR=", 0 }, { "REMOTE_HOST=", 0 }, { "REMOTE_IDENT=", 0 }, { "REMOTE_PORT=", 0 }, { "REMOTE_USER=", 0 }, { "REQUEST_METHOD=", 0 }, { "SCRIPT_FILENAME=", 0 }, { "SCRIPT_NAME=", 0 }, { "SCRIPT_URI=", 0 }, { "SCRIPT_URL=", 0 }, { "SERVER_ADMIN=", 0 }, { "SERVER_NAME=", 0 }, { "SERVER_PORT=", 0 }, { "SERVER_PROTOCOL=", 0 }, { "SERVER_SOFTWARE=", 0 }, { "TZ=", 0 }, { "USER_NAME=", 0 }, { 0, 0 } }; /* * End of user configurable items */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <ctype.h> #include <time.h> #include <pwd.h> #include <grp.h> #if !defined(bsdi) && !defined(hpux) && !defined(sun) && !defined(linux) && !defined(SCO5) && !defined(UWARE7) #include <sys/mode.h> #endif #if defined(sun) || defined(bsdi) || defined(sgi) || defined(SCO5) || defined(UWARE7) extern const char ** environ; #endif extern int errno; #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif #if (MAXPATHLEN < 1024) #undef MAXPATHLEN #define MAXPATHLEN 1024 #endif #define KEYLEN 128 /* Should be a multiple of sizeof(int) */ #define FPKEYDIR "/usr/local/frontpage/version4.0/apache-fp" #define KEYFILE "/usr/local/frontpage/version4.0/apache-fp/suidkey.%d" #define FPDIR "/usr/local/frontpage/version4.0/exes" /* Legal modules */ #define SHTML "/_vti_bin/shtml.exe" #define FPCOUNT "/_vti_bin/fpcount.exe" #define AUTHOR "/_vti_bin/_vti_aut/author.exe" #define ADMIN "/_vti_bin/_vti_adm/admin.exe" /* * Something is not quite right - give up */ void die(const char *msg) { char timebuf[26]; time_t t = time(0); strcpy(timebuf, ctime(&t)); timebuf[24] = '\0'; fprintf(stderr, "[%s] %s\n", timebuf, msg); printf("Content-Type: text/html\n\n<HTML>*-*-* :-| :^| :-/ :-( 8-( *-*-*\n<ul>\n<li>status=1\n<li> osstatus=0\n<li>msg=FrontPage security violation.\n<li>osmsg=\n</ul>\n"); exit(0); } /* * Remove any variable that is not known to be a standard CGI or OS * environment variable. Also, sanitizes the PATH. */ static void CleanEnvironment() { const char** pp; const char** ppi; struct SaveEnvVars* pOkEnv; for (ppi = pp = environ; *pp; pp++) { /* * Inefficient linear lookup; could be improved with binary search. */ for (pOkEnv = gSafeEnvVars; pOkEnv->szVar; pOkEnv++) { int iLen = pOkEnv->iLen; if (!iLen) pOkEnv->iLen = iLen = strlen(pOkEnv->szVar); if (strncmp(pOkEnv->szVar, *pp, iLen) == 0) break; } if (!strncmp(*pp, "PATH=", 5)) *ppi++ = CLEAN_PATH; else if (pOkEnv->szVar) *ppi++ = *pp; } *ppi = 0; } int main(int argc, char **argv) { struct passwd* pw = 0; const char* szFpUserName; const char* szFpExe = getenv("FPEXE"); const char* szFpUid = getenv("FPUID"); const char* szFpGid = getenv("FPGID"); const char* szFpFd = getenv("FPFD"); char* pEnd; char* pDir; uid_t iFpUid; uid_t iFpGid; uid_t iBinUid; int iFpFd; int iKeyFd; int iCount; char szKeyFile[MAXPATHLEN]; char szWork[MAXPATHLEN]; char inpKey[KEYLEN]; char refKey[KEYLEN]; struct stat fs; /* * Assure that this program was actually SUID'd to root */ if (geteuid()) /* * User recovery: Make sure fpexe is setuid to root */ die("FrontPage SUID Error: not running as root"); /* * Assure that the user the Web server runs as is a valid user */ if (!getpwuid(getuid())) /* * User recovery: Make sure that the Web server user is in /etc/passwd */ die("FrontPage SUID Error: invalid uid"); /* * Assure that we have the proper arguments (passed in the environment) */ if (!szFpExe || !szFpUid || !szFpGid || !szFpFd) /* * User recovery: Make sure fpexe is run from patched Apache server */ die("FrontPage SUID Error: invalid environment arguments"); /* * Validate the arguments */ if (strcmp(szFpExe, SHTML) != 0 && strcmp(szFpExe, FPCOUNT) != 0 && strcmp(szFpExe, AUTHOR) != 0 && strcmp(szFpExe, ADMIN) != 0) /* * User recovery: Make sure fpexe is only invoked to run FrontPage * server extension programs. */ die("FrontPage SUID Error: target program violation"); if (strlen(szFpExe) + strlen(FPDIR) + 1 > MAXPATHLEN) die("FrontPage SUID Error: path too long"); strcpy(szWork, FPDIR); strcat(szWork, szFpExe); iFpUid = strtol(szFpUid, &pEnd, 10); if (!pEnd || *pEnd) iFpUid = 0; if (iFpUid < LOWEST_VALID_UID || !(pw = getpwuid(iFpUid))) /* * User recovery: Make sure FrontPage user ids are above minimum */ die("FrontPage SUID Error: invalid target uid"); szFpUserName = strdup(pw->pw_name); iFpGid = strtol(szFpGid, &pEnd, 10); if (!pEnd || *pEnd) iFpGid = 0; if (iFpGid < LOWEST_VALID_GID || !getgrgid(iFpGid)) /* * User recovery: Make sure FrontPage group ids are above minimum */ die("FrontPage SUID Error: invalid target gid"); iFpFd = strtol(szFpFd, &pEnd, 10); if (!pEnd || *pEnd) iFpFd = -1; if (iFpFd < 0) /* * User recovery: Make sure fpexe is run from patched Apache server */ die("FrontPage SUID Error: invalid key file descriptor"); /* * Read the key from our server. And, while we're still root and have * access, read the key from the master key file. Verify the key matches. */ if (Vlstat(FPKEYDIR, &fs) == -1 || (fs.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) || fs.st_uid || !(S_ISDIR(fs.st_mode))) /* * User recovery is: set directory to be owned by by root with * permissions rwx--x--x. */ die("FrontPage SUID Error: key file directory is insecure"); #if defined(sun) && !defined(__SVR4) sprintf(szKeyFile, KEYFILE, (int)getpgrp(0)); #else sprintf(szKeyFile, KEYFILE, (int)getpgrp()); #endif if (Vstat(szKeyFile, &fs) == -1 || (fs.st_mode & (S_IRWXG | S_IRWXO)) || fs.st_uid) /* * User recovery is: Make sure the key file is properly protected * (owned by root, permissions r**------), restart patched Apache * server. */ die("FrontPage SUID Error: key file security violation"); iKeyFd = open(szKeyFile, O_RDONLY); if (iKeyFd < 0) /* * User recovery is: Make sure fpexe is run from patched Apache * server, restart the patched Apache server. */ die("FrontPage SUID Error: could not open key file" ); iCount = read(iKeyFd, refKey, sizeof(refKey)); close(iKeyFd); if (iCount != sizeof(refKey)) /* * User recovery is: Make sure fpexe is run from patched Apache * server, restart the patched Apache server. */ die("FrontPage SUID Error: could not read valid key from key file"); iCount = read(iFpFd, inpKey, sizeof(inpKey)); close(iFpFd); if (iCount != sizeof(inpKey)) /* * User recovery is: Make sure fpexe is run from patched Apache server */ die("FrontPage SUID Error: could not read valid input key"); if (memcmp(inpKey, refKey, sizeof(refKey)) != 0) /* * User recovery is: Make sure fpexe is run from patched Apache server */ die("FrontPage SUID Error: key security violation"); /* * Change user and group IDs to be the indicated user */ if (setgid(iFpGid) == -1 || initgroups(szFpUserName, iFpGid) == -1) /* * User recovery: Make sure user is properly registered in * /etc/passwd and /etc/group. */ die("FrontPage SUID Error: setgid() failed"); if (setuid(iFpUid) == -1) /* * User recovery: Make sure user is properly registered in * /etc/passwd. */ die("FrontPage SUID Error: setuid() failed"); /* * Validate the target directory. */ iBinUid = 0; if (pw = getpwnam("bin")) iBinUid = pw->pw_uid; pDir = strrchr(szWork, '/'); *pDir = 0; if (Vlstat(szWork, &fs) == -1 || (fs.st_mode & (S_IWGRP | S_IWOTH)) || (fs.st_uid != iBinUid && fs.st_uid != 0) || !(S_ISDIR(fs.st_mode))) /* * User recovery is: make sure FrontPage exe programs are available, * set directory to be owned by bin or root and have permissions * rwx*-x*-x. */ die("FrontPage SUID Error: target directory not found or insecure"); *pDir = '/'; /* * Validate the target program */ if (Vstat(szWork, &fs) == -1 || ((fs.st_mode & (S_IWGRP | S_IWOTH)) || (fs.st_mode & (S_ISUID | S_ISGID)) || (fs.st_uid != iBinUid && fs.st_uid != 0))) /* * User recovery is: make sure FrontPage exe programs are available, * set programs to be owned by bin or root and have permissions * rwx*-x*-x. */ die("FrontPage SUID Error: target program not found or insecure"); *pDir = '/'; /* * Make sure the environment contains no unsafe values. */ CleanEnvironment(); /* * Run the specified program. */ argv[0] = szWork; umask(022); execv(argv[0], argv); /* * We should never get here. Exit with error. */ return (1); }