4.7. suEXEC on Unix
The vulnerability of servers running scripts is a continual source of concern to the Apache Group. Unix systems provide a special method of running CGIs that gives much better security via a wrapper. A wrapper is a program that wraps around another program in order to change the way it operates. Usually this is done by changing its environment in some way; in this case, by making sure it runs as if it had been invoked by an appropriate user. The basic security problem is that any program or script run by Apache has the same permissions as Apache itself. Of course, these permissions are not those of the superuser, but, even so, Apache tends to have permissions powerful enough to impair the moral development of a clever hacker if he could get his hands on them. Also, in environments where there are many users who can write scripts independently of each other, it is a good idea to insulate them from each other's bugs, as far as is possible.
suEXEC reduces the risk by changing the permissions given to a program or script launched by Apache. In order to use it you should understand the Unix concepts of user and group execute permissions on files and directories. suEXEC is executed whenever an HTTP request is made for a script or program that has ownership or group membership permissions different from those of Apache itself, which will normally be those appropriate to webuser of webgroup.
The documentation says that suEXEC is quite deliberately complicated so that "it will only be installed by users determined to use it." However, we found it no more difficult than Apache itself to install, so you should not be deterred from using what may prove to be a very valuable defence. If you are interested, please consult the documentation and be guided by it. What we have written in this section is intended only to help and encourage, not to replace the words of wisdom. See http://www2.idiscover.co.uk/apache/docs/suexec.html.
To install suEXEC to run with the demonstration site site.suexec, go to the support subdirectory below the location of your Apache source code. Edit suexec.h to make the following changes to suit your installation. What we did, to suit our environment, is shown marked by /**CHANGED**/:
/* * 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 "webuser" /**CHANGED**/ #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
The point here is that many systems have "privileged" users below some number (e.g. root, daemon, lp, and so on), so we can use this setting to avoid any possibility of running a script as one of these users:
/* * 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 // see UID above #endif
Similarly, there may be privileged groups:
/* * USERDIR_SUFFIX -- Define to be the subdirectory under users' * home directories where suEXEC access should * be allowed. All executables under this directory * will be executable by suEXEC as the user so * they should be "safe" programs. If you are * using a "simple" UserDir directive (ie. one * without a "*" in it) this should be set to * the same value. suEXEC will not work properly * in cases where the UserDir directive points to * a location that is not the same as the user's * home directory as referenced in the passwd 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 "/usr/www/cgi-bin" /**CHANGED**/ #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/www/suexec.log" /**CHANGED**/ #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/www/site.suexec/htdocs" /**CHANGED**/ #endif /* * SAFE_PATH -- Define a safe PATH environment to pass to CGI executables. * */ #ifndef SAFE_PATH #define SAFE_PATH "/usr/local/bin:/usr/bin:/bin" #endif
Compile the file to make suEXEC executable by typing:
and copy it to a sensible location (this will very likely be different on your site -- replace /usr/local/bin with whatever is appropriate) alongside Apache itself with:
cp suexec /usr/local/bin
You then have to set its permissions properly by making yourself the superuser (or persuading the actual, human superuser to do it for you if you are not allowed to) and typing:
chown root /usr/local/bin/suexec chmod 4711 /usr/local/bin/suexec
The first line gives suEXEC the owner root; the second sets the setuserid execution bit for file modes.
You then have to tell Apache where to find the suEXEC executable by editing ...src/include/httpd.h. We looked for "suEXEC" and changed it thus:
/* The path to the suExec wrapper; can be overridden in Configuration */ #ifndef SUEXEC_BIN #define SUEXEC_BIN "/usr/local/bin/suexec" /**CHANGED**/ #endif
This line was originally:
#define SUEXEC_BIN HTTPD_ROOT "/sbin/suexec"
Notice that the macro HTTPD_ROOT has been removed. It is easy to leave it in by mistake -- we did the first time around -- but it prepends /usr/local/apache (or whatever you may have changed it to) to the path you type in, which may not be what you want to happen. Having done this, you remake Apache by getting into the .../src directory and typing:
make cp httpd /usr/local/bin
or wherever you want to keep the executable. When you start Apache, nothing appears to be different, but a message appears in .../logs/error_log :
suEXEC mechanism enabled (wrapper: /usr/local/bin/suexec)
We think that something as important as suEXEC should have a clearly visible indication on the command line, and that an entry in a log file is not immediate enough.
To turn suEXEC off, you simply remove the executable, or, more cautiously, rename it to, say, suexec.not. Apache then can't find it and carries on without comment.
Once suEXEC is running, it applies many tests to any CGI or server-side include (SSI) script invoked by Apache. If any of the tests fail, a note will appear in the suexec.log file that you specified (as the macro LOG_EXEC in suexecx.h) when you compiled suEXEC. A comprehensive list appears in the documentation and also in the source. Many of these tests can only fail if there is a bug in Apache, suEXEC, or the operating system, or if someone is attempting to misuse suEXEC. We list here the notes that you are likely to encounter in normal operation, since you should never come across the others. If you do, suspect the worst:
If all these hurdles are passed, then the program executes. In setting up your system, you have to bear these hurdles in mind.
Note that once suEXEC has decided it will execute your script, it then makes it even safer by cleaning the environment -- that is, deleting any environment variables not on its list of safe ones and replacing the PATH with the path defined in SAFE_PATH in suexec.h. The list of safe environment variables can be found in .../src/support/suexec.c, in the variable safe_env_lst. This list includes all the standard variables passed to CGI scripts. Of course, this means that any special-purpose variables you set with SetEnv or PassEnv directives will not make it to your CGI scripts unless you add them to suexec.c.
4.7.1. A Demonstration of suEXEC
So far, for the sake of simplicity, we have been running everything as root, to which all things are possible. To demonstrate suEXEC we need to create a humble but ill-intentioned user, Peter, who will write and run a script called badcgi.cgi intending to do harm to those around. badcgi.cgi simply deletes /usr/victim/victim1 as a demonstration of its power -- but it could do many worse things. This file belongs to webuser and webgroup. Normally, Peter, who is not webuser and does not belong to webgroup, would not be allowed to do anything to it, but if he gets at it through Apache (undefended by suEXEC ) he can do what he likes.
Peter creates himself a little web site in his home directory, /home/peter, which contains the directories:
conf logs public_html
and the usual file go:
httpd -d /home/peter
The Config file is:
User webuser Group webgroup ServerName www.butterthlies.com ServerAdmin email@example.com UserDir public_html AddHandler cgi-script cgi
Most of this is relevant in the present situation. By specifying webuser and webgroup, we give any program executed by Apache that user and group. In our guise of Peter, we are going to ask the browser to log onto httpd://www.butter-thlies.com/~peter -- that is, to the home directory of Peter on the computer whose port answers to www.butterthlies.com. Once in that home directory, we are referred to the UserDir public_html, which acts pretty much the same as DocumentRoot in the web sites we have been playing with.
Peter puts an innocent-looking Butterthlies form, form_summer.html, into public_html. But, it conceals a viper! Instead of having ACTION="mycgi.cgi", as innocent forms do, this one calls badcgi.cgi, which looks like this:
#!/bin/sh echo "content-type: text/plain" echo rm -f /usr/victim/victim1
This is a script of unprecedented villainy, whose last line will utterly destroy and undo the innocent file victim1. Remembering that any CGI script executed by Apache has only the user and group permissions specified in the Config file -- that is, webuser and webgroup, we go and make the target file the same, by logging on as root and typing:
chown webuser:webgroup /usr/victim chown webuser:webgroup /usr/victim/victim1
Now, if we log on as Peter and execute badcgi.cgi, we are roundly rebuffed:
./badcgi.cgi rm: /usr/victim/victim1: Permission denied
This is as it should be -- Unix security measures are working. However, if we do the same thing under the cloak of Apache, by logging on as root and executing:
and then, on the browser, accessing http://www.butterthlies.com/~peter, opening form_summer.html, and clicking the Submit button at the bottom of the form, we see that the browser is accessing www.butterthlies.com/~peter/badcgi.cgi and we get the warning message:
Document contains no data
This statement is regrettably true because badcgi.cgi now has the permissions of webuser and webgroup ; it can execute in the directory /usr/victim, and it has removed the unfortunate victim1 in insolent silence.
So much for what an in-house Bad Guy could do before suEXEC came along. If we now replace victim1, stop Apache, rename suEXEC.not to suEXEC, restart Apache (checking that the .../logs/error_log file shows that suEXEC started up), and click Submit on the browser again, we get the following comforting message:
Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request. Please contact the server administrator, firstname.lastname@example.org and inform them of the time the error occurred, and anything you might have done that may have caused the error.
The error log contains the following:
[Tue Sep 15 13:42:53 1998] [error] malformed header from script. Bad header=suexec running: /home/peter/public_html/badcgi.cgi
Copyright © 2001 O'Reilly & Associates. All rights reserved.