randy 97/01/12 11:20:35 Modified: src CHANGES Log: Add suexec CHANGES Revision Changes Path 1.118 +11 -0 apache/src/CHANGES Index: CHANGES =================================================================== RCS file: /export/home/cvs/apache/src/CHANGES,v retrieving revision 1.117 retrieving revision 1.118 diff -C3 -r1.117 -r1.118 *** CHANGES 1997/01/12 16:58:00 1.117 --- CHANGES 1997/01/12 19:18:46 1.118 *************** *** 1,5 **** --- 1,16 ---- Changes with Apache 1.2b5 + *) Several security enhancements to suexec wrapper. It is _highly_ + recommended that previously installed versions of the wrapper + be replaced with this version. [Randy Terbush, Jason Dour] + + - ~user execution now properly restricted to ~user's home + directory and below. + - execution restricted to UID/GID > 100 + - restrict passed environment to known variables + - call setgid() before initgroups() (portability fix) + - remove use of setenv() (portability fix) + *) Add HTTP/1.0 response forcing. [Ben Laurie] *) Add access control via environment variables. [Ben Laurie]
Modified: support suexec.c suexec.h Log: Add serveral security enhancements. - ~user execution now properly restricted to ~user's home directory and below. - execution restricted to UID/GID > 100 - restrict passed environment to known variables - call setgid() before initgroups() (portability fix) - remove use of setenv() (portability fix) Reviewed-by: Marc Slemko, Jason Dour, Randy Terbush Revision Changes Path 1.11 +99 -18 apache/support/suexec.c Index: suexec.c =================================================================== RCS file: /export/home/cvs/apache/support/suexec.c,v retrieving revision 1.10 retrieving revision 1.11 diff -C3 -r1.10 -r1.11 *** suexec.c 1997/01/01 18:26:17 1.10 --- suexec.c 1997/01/12 19:20:33 1.11 *************** *** 81,89 **** --- 81,132 ---- #include <time.h> #include <sys/stat.h> + #define CLEAN_ENV_BUF 256 + extern char **environ; static FILE *log; + char *safe_env_lst[] = + { + "AUTH_TYPE", + "CONTENT_LENGTH", + "CONTENT_TYPE", + "DATE_GMT", + "DATE_LOCAL", + "DOCUMENT_NAME", + "DOCUMENT_PATH_INFO", + "DOCUMENT_ROOT", + "DOCUMENT_URI", + "FILEPATH_INFO", + "GATEWAY_INTERFACE", + "LAST_MODIFIED", + "PATH_INFO", + "PATH_TRANSLATED", + "QUERY_STRING", + "QUERY_STRING_UNESCAPED", + "REMOTE_ADDR", + "REMOTE_HOST", + "REMOTE_IDENT", + "REMOTE_PORT", + "REMOTE_USER", + "REDIRECT_QUERY_STRING", + "REDIRECT_STATUS", + "REDIRECT_URL", + "REQUEST_METHOD", + "SCRIPT_FILENAME", + "SCRIPT_NAME", + "SCRIPT_URI", + "SCRIPT_URL", + "SERVER_ADMIN", + "SERVER_NAME", + "SERVER_PORT", + "SERVER_PROTOCOL", + "SERVER_SOFTWARE", + "USER_NAME", + NULL + }; + + static void err_output(const char *fmt, va_list ap) { time_t timevar; *************** *** 120,128 **** return; } ! int main(int argc, char *argv[], char **env) { - int doclen; /* length of the docroot */ int userdir = 0; /* ~userdir flag */ uid_t uid; /* user information */ gid_t gid; /* target group placeholder */ --- 163,208 ---- return; } ! void clean_env() ! { ! char pathbuf[512]; ! char **cleanenv; ! char **ep; ! int cidx = 0; ! int idx; ! ! ! if ((cleanenv = (char **)malloc(CLEAN_ENV_BUF * (sizeof(char *)))) == NULL) { ! log_err("failed to malloc env mem\n"); ! exit(120); ! } ! ! for (ep = environ; *ep; ep++) { ! if (!strncmp(*ep, "HTTP_", 5)) { ! cleanenv[cidx] = *ep; ! cidx++; ! } ! else { ! for (idx = 0; safe_env_lst[idx]; idx++) { ! if (!strncmp(*ep, safe_env_lst[idx], strlen(safe_env_lst[idx]))) { ! cleanenv[cidx] = *ep; ! cidx++; ! break; ! } ! } ! } ! } ! ! sprintf(pathbuf, "PATH=%s", SAFE_PATH); ! cleanenv[cidx] = pathbuf; ! cleanenv[++cidx] = NULL; ! ! environ = cleanenv; ! free(cleanenv); ! } ! ! int main(int argc, char *argv[]) { int userdir = 0; /* ~userdir flag */ uid_t uid; /* user information */ gid_t gid; /* target group placeholder */ *************** *** 211,217 **** /* * Get the current working directory, as well as the proper * document root (dependant upon whether or not it is a ! * ~userdir request. Error out if we cannot get either one, * or if the current working directory is not in the docroot. * Use chdir()s and getcwd()s to avoid problems with symlinked * directories. Yuck. --- 291,297 ---- /* * Get the current working directory, as well as the proper * document root (dependant upon whether or not it is a ! * ~userdir request). Error out if we cannot get either one, * or if the current working directory is not in the docroot. * Use chdir()s and getcwd()s to avoid problems with symlinked * directories. Yuck. *************** *** 223,228 **** --- 303,309 ---- if (userdir) { if (((chdir(pw->pw_dir)) != 0) || + ((chdir(USERDIR_SUFFIX)) != 0) || ((getcwd(dwd, MAXPATHLEN)) == NULL) || ((chdir(cwd)) != 0)) { *************** *** 239,247 **** exit(108); } } ! ! doclen = strlen(dwd); ! if ((strncmp(cwd, dwd, doclen)) != 0) { log_err("command not in docroot (%s/%s)\n", cwd, cmd); exit(109); } --- 320,327 ---- exit(108); } } ! ! if ((strncmp(cwd, dwd, strlen(dwd))) != 0) { log_err("command not in docroot (%s/%s)\n", cwd, cmd); exit(109); } *************** *** 303,320 **** } /* ! * Error out if attempt is made to execute as root. Tsk tsk. */ ! if (pw->pw_uid == 0) { ! log_err("cannot run as uid 0 (%s)\n", cmd); exit(116); } /* ! * Error out if attempt is made to execute as root group. Tsk tsk. */ ! if (gr->gr_gid == 0) { ! log_err("cannot run as gid 0 (%s)\n", cmd); exit(117); } --- 383,404 ---- } /* ! * Error out if attempt is made to execute as root or as ! * a UID less than UID_MIN. Tsk tsk. */ ! if ((pw->pw_uid == 0) || ! (pw->pw_uid < UID_MIN)) { ! log_err("cannot run as forbidden uid (%d/%s)\n", pw->pw_uid, cmd); exit(116); } /* ! * Error out if attempt is made to execute as root group ! * or as a GID less than GID_MIN. Tsk tsk. */ ! if ((gr->gr_gid == 0) || ! (gr->gr_gid < GID_MIN)) { ! log_err("cannot run as forbidden gid (%d/%s)\n", gr->gr_gid, cmd); exit(117); } *************** *** 333,339 **** */ uid = pw->pw_uid; gid = gr->gr_gid; ! if ((initgroups(target_uname,gid) != 0) || ((setgid(gid)) != 0)) { log_err("failed to setgid (%ld: %s/%s)\n", gid, cwd, cmd); exit(118); } --- 417,423 ---- */ uid = pw->pw_uid; gid = gr->gr_gid; ! if (((setgid(gid)) != 0) || (initgroups(pw->pw_name,gid) != 0)) { log_err("failed to setgid (%ld: %s/%s)\n", gid, cwd, cmd); exit(118); } *************** *** 346,360 **** exit(119); } ! if ((setenv("PATH", SAFE_PATH, 1)) != 0) { ! log_err("cannot reset environment PATH\n"); ! exit(120); ! } /* * Execute the command, replacing our image with its own. */ ! execve(cmd, &argv[3], env); /* * (I can't help myself...sorry.) --- 430,441 ---- exit(119); } ! clean_env(); /* * Execute the command, replacing our image with its own. */ ! execv(cmd, argv); /* * (I can't help myself...sorry.) 1.7 +30 -0 apache/support/suexec.h Index: suexec.h =================================================================== RCS file: /export/home/cvs/apache/support/suexec.h,v retrieving revision 1.6 retrieving revision 1.7 diff -C3 -r1.6 -r1.7 *** suexec.h 1997/01/01 18:26:18 1.6 --- suexec.h 1997/01/12 19:20:34 1.7 *************** *** 68,73 **** --- 68,103 ---- #endif /* + * UID_MIN -- Define this as the lowest UID allowed to be a target user + * for suEXEC. For most systems, 500 or 100 is common. + */ + #ifndef UID_MIN + #define UID_MIN 500 + #endif + + /* + * GID_MIN -- Define this as the lowest GID allowed to be a target group + * for suEXEC. For most systems, 100 is common. + */ + #ifndef GID_MIN + #define GID_MIN 100 + #endif + + /* + * USERDIR_SUFFIX -- Define to be the same as the UserDir in the conf + * file. If you have VirtualHosts with a different + * UserDir for each, you will need to define them to + * all reside in one parent directory; then name that + * parent directory here. IF THIS IS NOT DEFINED + * PROPERLY, ~USERDIR CGI REQUESTS WILL NOT WORK! + * See the suEXEC documentation for more detailed + * information. + */ + #ifndef USERDIR_SUFFIX + #define USERDIR_SUFFIX "public_html" + #endif + + /* * LOG_EXEC -- Define this as a filename if you want all suEXEC * transactions and errors logged for auditing and * debugging purposes.