You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Marc Slemko <ma...@znep.com> on 1997/01/23 03:32:56 UTC

Re: suexec.c code changes required for Apache 1.2b4/IRIX 6.2 (again) (fwd)

FYI.  It adds checks in the password file for valid shell, etc. and a
allow/deny file.  I'm not suggesting using any of it directly, nor
suggesting we consider this functionality before 1.2, but it can be looked
at if we ever implement something similar in suexec.  (yes, a lot of it
was stolen from cgiwrap) 

---------- Forwarded message ----------
Date: Wed, 22 Jan 1997 11:44:24 -0800 (PST)
From: Ian Reddy <ia...@sfu.ca>
Reply-To: Ian_Reddy@sfu.ca
To: Marc Slemko <ma...@znep.com>
Subject: Re: suexec.c code changes required for Apache 1.2b4/IRIX 6.2 (again)

For better or worse here's the suexec.c that I've ended up using
followed by the suexec.h that supports the changes. The CheckUser,
UserInFile, and FileExists routines are carved out of cgiwrap3.5,
which has a covenant "You may not modify and redistribute
CGIwrap without my permission. ...  -- Nathan Neulinger (nneul@umr.edu)",
and I've included my NIS/netgroups mods:



/* ====================================================================
 * Copyright (c) 1995-1997 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
 * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
 *
 ***********************************************************************
 *
 * NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
 *         editing this code might open up your system in unexpected 
 *         ways to would-be crackers.  Every precaution has been taken 
 *         to make this code as safe as possible; alter it at your own
 *         risk.
 *
 ***********************************************************************
 *
 *
 */


#include "suexec.h"

#include <sys/param.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdarg.h>
#include <strings.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <sys/stat.h>

#define CLEAN_ENV_BUF 256

extern char **environ;
static FILE *log;

static int CheckUser(struct passwd *user);
static int UserInFile(char *filename, char *user);
static int FileExists(char *path);

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",
    "TZ",
    "USER_NAME",
    NULL
};


static void err_output(const char *fmt, va_list ap)
{
    time_t timevar;
    struct tm *lt;

    if (!log)
	if ((log = fopen(LOG_EXEC, "a")) == NULL) {
	    fprintf(stderr, "failed to open log file\n");
	    perror("fopen");
	    exit(1);
	}

    time(&timevar);
    lt = localtime(&timevar);
    
    fprintf(log, "[%.2d:%.2d:%.2d %.2d-%.2d-%.2d]: ", lt->tm_hour, lt->tm_min,
	    lt->tm_sec, lt->tm_mday, (lt->tm_mon + 1), lt->tm_year);
    
    vfprintf(log, fmt, ap);

    fflush(log);
    return;
}

void log_err(const char *fmt, ...)
{
#ifdef LOG_EXEC
    va_list     ap;

    va_start(ap, fmt);
    err_output(fmt, ap);
    va_end(ap);
#endif /* LOG_EXEC */
    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  */
    char *target_uname;     /* target user name          */
    char *target_gname;     /* target group name         */
    char *prog;             /* name of this program      */
    char *cmd;              /* command to be executed    */
    char cwd[MAXPATHLEN];   /* current working directory */
    char dwd[MAXPATHLEN];   /* docroot working directory */
    struct passwd *pw;      /* password entry holder     */
    struct group *gr;       /* group entry holder        */
    struct stat dir_info;   /* directory info holder     */
    struct stat prg_info;   /* program info holder       */

    

    /*
     * If there are a proper number of arguments, set
     * all of them to variables.  Otherwise, error out.
     */
    prog = argv[0];
    if (argc < 4) {
	log_err("too few arguments\n");
	exit(101);
    }
    target_uname = argv[1];
    target_gname = argv[2];
    cmd = argv[3];

    /*
     * Check existence/validity of the UID of the user
     * running this program.  Error out if invalid.
     */
    uid = getuid();
    if ((pw = getpwuid(uid)) == NULL) {
	log_err("invalid uid: (%ld)\n", uid);
	exit(102);
    }
    
    /*
     * Check to see if the user running this program
     * is the user allowed to do so as defined in
     * suexec.h.  If not the allowed user, error out.
     */
    if (strcmp(HTTPD_USER, pw->pw_name)) {
	log_err("user mismatch (%s)\n", pw->pw_name);
	exit(103);
    }
    
    /*
     * Check for a '/' in the command to be executed,
     * to protect against attacks.  If a '/' is
     * found, error out.  Naughty naughty crackers.
     */
    if ((strchr(cmd, '/')) != NULL ) {
	log_err("invalid command (%s)\n", cmd);
	exit(104);
    }

    /*
     * Check to see if this is a ~userdir request.  If
     * so, set the flag, and remove the '~' from the
     * target username.
     */
    if (!strncmp("~", target_uname, 1)) {
	target_uname++;
	userdir = 1;
    }

    /*
     * Error out if the target username is invalid.
     */
    if ((pw = getpwnam(target_uname)) == NULL) {
	log_err("invalid target user name: (%s)\n", target_uname);
	exit(105);
    }

    /*
     * Error out if the user is not allowed to use CGI scripts
     */
    if (!CheckUser(pw)) {
        log_err("target user is denied cgi execution: (%s)\n", pw->pw_name);
        exit(200);
    }

    /*
     * Error out if the target group name is invalid.
     */
    if ((gr = getgrnam(target_gname)) == NULL) {
	log_err("invalid target group name: (%s)\n", target_gname);
	exit(106);
    }

    /*
     * 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);
    }

    /*
     * Log the transaction here to be sure we have an open log 
     * before we setuid().
     */
    log_err("uid: (%s/%s) gid: (%s/%s) %s\n",
             target_uname, pw->pw_name,
             target_gname, gr->gr_name,
             cmd);

    /*
     * Initialize the group access list for the target user,
     * and setgid() to the target group. If unsuccessful, error out.
     */
    uid = pw->pw_uid;
    /* initgroups() trashes the gr-> structure, set and use gid from now on */
    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);
    }

    /*
     * setuid() to the target user.  Error out on fail.
     */
    if ((setuid(uid)) != 0) {
	log_err("failed to setuid (%ld: %s/%s)\n", uid, cwd, cmd);
	exit(119);
    }

    /*
     * 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.
     */
    if (getcwd(cwd, MAXPATHLEN) == NULL) {
        log_err("cannot get current working directory\n");
        exit(107);
    }
    
    if (userdir) {
        if (((chdir(pw->pw_dir)) != 0) ||
            ((chdir(USERDIR_SUFFIX)) != 0) ||
	    ((getcwd(dwd, MAXPATHLEN)) == NULL) ||
            ((chdir(cwd)) != 0))
        {
            log_err("cannot get docroot information (%s)\n", pw->pw_dir);
            exit(108);
        }
    }
    else {
        if (((chdir(DOC_ROOT)) != 0) ||
	    ((getcwd(dwd, MAXPATHLEN)) == NULL) ||
	    ((chdir(cwd)) != 0))
        {
            log_err("cannot get docroot information (%s)\n", DOC_ROOT);
            exit(108);
        }
    }

    if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
        log_err("command not in docroot (%s/%s)\n", cwd, cmd);
        exit(109);
    }

    /*
     * Stat the cwd and verify it is a directory, or error out.
     */
    if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
	log_err("cannot stat directory: (%s)\n", cwd);
	exit(110);
    }

    /*
     * Error out if cwd is writable by others.
     */
    if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
	log_err("directory is writable by others: (%s)\n", cwd);
	exit(111);
    }

    /*
     * Error out if we cannot stat the program.
     */
    if (((lstat(cmd, &prg_info)) != 0) ||
        (S_ISLNK(prg_info.st_mode)) ||
        (!S_ISREG(prg_info.st_mode))) {
	log_err("cannot stat program: (%s)\n", cmd);
	exit(112);
    }

    /*
     * Error out if the program is writable by others.
     */
    if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
	log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
	exit(113);
    }

    /*
     * Error out if the file is setuid or setgid.
     */
    if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
	log_err("file is either setuid or setgid: (%s/%s)\n",cwd,cmd);
	exit(114);
    }

    /*
     * Error out if the target name/group is different from
     * the name/group of the cwd or the program.
     */
    if ((pw->pw_uid != dir_info.st_uid) ||
	(gid != dir_info.st_gid) ||
	(pw->pw_uid != prg_info.st_uid) ||
	(gid != prg_info.st_gid))
    {
	log_err("target uid/gid (%ld/%ld) mismatch with directory (%ld/%ld) or program (%ld/%ld)\n",
		 pw->pw_uid, gid,
		 dir_info.st_uid, dir_info.st_gid,
		 prg_info.st_uid, prg_info.st_gid);
	exit(115);
    }

    clean_env();
    
    /*
     * Execute the command, replacing our image with its own.
     */
    execv(cmd, &argv[3]);

    /*
     * (I can't help myself...sorry.)
     *
     * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
     * EARTH-shattering kaboom!
     *
     * Oh well, log the failure and error out.
     */
    log_err("exec failed (%s)\n", cmd);
    exit(255);
}

/*
 * Perform checks on the userid
 */
int CheckUser(struct passwd *user)
{
        int deny_exists, allow_exists;
        int in_deny, in_allow;

#if defined(CONF_ACCESS)
        /*
         * Error out if the target username is locked out.
         */
        if (!strcmp(user->pw_passwd, "*") || strcmp(user->pw_shell, "/bin/csh")) {
	    log_err("user account is disabled\n");
            return(0);
        }

        deny_exists = FileExists(CONF_DENYFILE);
        allow_exists = FileExists(CONF_ALLOWFILE);
        in_deny = 0;
        in_allow = 0;

        if ( deny_exists ) {
                in_deny = UserInFile(CONF_DENYFILE, user->pw_name);
        }

        if ( allow_exists ) {
                in_allow = UserInFile(CONF_ALLOWFILE, user->pw_name);
        }

        if ( !deny_exists && !allow_exists ) {
	    log_err("access control files not found\n");
            return(0);
        }

        if ( in_allow && in_deny ) {
	    log_err("user in both allow and deny files\n");
            return(0);
        }

        if ( allow_exists && !in_allow ) {
	    log_err("user not in allow file\n");
            return(0);
        }

        if ( deny_exists && in_deny ) {
	    log_err("user in deny file\n");
            return(0);
        }
#endif
        return(1);
}

/*
 * Return true if 'user' is listed in file 'filename'
 */
int UserInFile(char *filename, char *user)
{
        FILE *file;
        char temp[200];
#if defined(USE_NETGROUPS)
        static char *netgroup;
        static char *machine = NULL;
        static char *domain = NULL;
#endif

        if ( (file=fopen(filename,"r")) == NULL ) {
	    log_err("couldn't open user list file\n");
            return(0);
        }

        while ( !feof(file) ) {
                fscanf(file,"%s",temp);
                if ( !strcmp(temp,user) ) {
                        fclose(file);
                        return 1;
                }
#if defined(USE_NETGROUPS)
                else if ( temp[0] == '@' ) {
                        netgroup = temp;
                        netgroup++;
                        if ( innetgr(netgroup, machine, user, domain)) {
                                fclose(file);
                                return 1;
                        }
                }
#endif
        }
        fclose(file);

        return 0;
}

/*
 * Check that a file exists
 */
int FileExists(char *path)
{
        struct stat fileStat; /* For checking file status */

        if ( stat(path, &fileStat) ) {
                return 0;
        }

        if ( !S_ISREG(fileStat.st_mode) ) {
                return 0;
        }

        return 1;
}







/* ====================================================================
 * Copyright (c) 1995-1997 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
 * suexec.h -- user-definable variables for the suexec wrapper code.
 */


#ifndef _SUEXEC_H
#define _SUEXEC_H

/*
 * HTTPD_USER -- Define as the username under which Apache normally
 *               runs.  This is the only user allowed to execute
 *               this program.
 */
#ifndef HTTPD_USER
#define HTTPD_USER "nobody"
#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 100
#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 "pub_html"
#endif

/*
 * LOG_EXEC -- Define this as a filename if you want all suEXEC
 *             transactions and errors logged for auditing and
 *             debugging purposes.
 */
#ifndef LOG_EXEC
#define LOG_EXEC "/usr/local/etc/httpd/logs/cgi.log" /* Need me? */
#endif

/*
 * DOC_ROOT -- Define as the DocumentRoot set for Apache.  This
 *             will be the only hierarchy (aside from UserDirs)
 *             that can be used for suEXEC behavior.
 */
#ifndef DOC_ROOT
#define DOC_ROOT "/usr/local/etc/httpd/htdocs"
#endif

/*
 * SAFE_PATH -- Define a safe PATH environment to pass to CGI executables.
 *
 */
#ifndef SAFE_PATH
#define SAFE_PATH "/usr/local/bin:/usr/sbin:/usr/bsd:/sbin:/usr/bin"
#endif

/* CONF_ALLOWFILE:
 *      Defined to be the filename listing users allowed to use cgi scripts.
 */
/* CONF_DENYFILE:
 *      Defined to be the filename listing users disallowed use of cgi scripts.
 */
/* CONF_ACCESS:
 *      Defined if you have enabled access control checking of the allow/deny
 *      files.
 */
#define CONF_ALLOWFILE "/usr/local/etc/cgiwrap.allow"
#define CONF_DENYFILE  "/usr/local/etc/cgiwrap.deny"
#define CONF_ACCESS

#endif  /* _SUEXEC_H */







-- 
  Ian Reddy, Senior Systems Consultant  E-mail:    Ian_Reddy@sfu.ca
  Academic Computing Services, AD1021              ian@sfu.ca
  Simon Fraser University               Telephone: (604) 291-3936
  Burnaby, B.C. Canada V5A 1S6          Fax:       (604) 291-4242