home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  

Book HomeApache: The Definitive GuideSearch this book

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**/
 * 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

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

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
 *                   See the suEXEC documentation for more detailed
 *                   information.
#define USERDIR_SUFFIX "/usr/www/cgi-bin"        /**CHANGED**/
 * 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**/
 * 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**/
 * SAFE_PATH -- Define a safe PATH environment to pass to CGI executables.
#ifndef SAFE_PATH
#define SAFE_PATH "/usr/local/bin:/usr/bin:/bin"

Compile the file to make suEXEC executable by typing:

make suexec

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**/

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:

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[43] in .../logs/error_log :

[43]In v1.3.1 this message didn't appear unless you included the line LogLevel debug in your Config file. In later versions it will appear automatically.

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:

  • Does the target program name have a "/" or ".." in its path? These are unsafe and not allowed.

  • Does the user who owns the target script exist on the system? Since user IDs can be deleted without deleting files owned by them, and some versions of tar, cpio, and the like can create files with silly user IDs (if run by root), this is a sensible check to make.

  • Does the group this user belongs to exist? As with user IDs, it is possible to create files with nonexistent groups.

  • Is the user not the superuser? suEXEC won't let root execute scripts online.

  • Is the user ID above the minimum ID number specified in suexec.h ? Many systems reserve user IDs below some number for certain powerful users -- not as powerful as root, but more powerful than mere mortals -- for example, the lpd daemon, backup operators, and so forth. This allows you to prevent their use for CGIs.

  • Is the user's group not the superuser's group? suEXEC won't let root's group execute scripts online.

  • Is the group ID above the minimum number specified? Again, this is to prevent the misuse of system groups.

  • Is this directory below the server's document root or, if for a UserDir, is the directory below the user's document root?

  • Is this directory not writable by anyone else? We don't want to open the door to all comers.

  • Does the target script exist? If not, it can hardly be run.

  • Is it only writable by the owner?

  • Is the target program not setuid or setgid ? We don't want visitors playing silly jokes with permissions.

  • Is the target user the owner of the script?

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:


and the usual file go:

httpd -d /home/peter

The Config file is:

User webuser
Group webgroup
ServerName www.butterthlies.com
ServerAdmin sales@butterthlies.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:

echo "content-type: text/plain"
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:

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, sales@butterthlies.com 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

Ha, ha!

Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.