|
Using Apache with Suexec on Linux

By: Ken Coar
Friday, January 21, 2000 01:07:47 PM EST
URL: http://www.linuxplanet.com/linuxplanet/tutorials/1445/1/
Executing CGI Scripts as Other UsersThe Apache Web server, like most if not all of the others in common
use today, lets you execute arbitrarily complex operations
through the use of CGI scripts. These can involve database
lookups, system administration functions, real-time control
of machinery, online payments, or almost anything else
you can think of.
Ordinarily, all of these things occur in the context of the user
running the Apache server itself (typically nobody
on Linux systems). This is fine when you're using a system that
is owned and used by a single entity...but what if you're an
ISP with multiple companies being hosted on your system? Or an
educational institution with faculty who want to be able to
execute their own scripts? Either everything has to be accessible
to the Apache nobody user, or you have to run
multiple instances of Apache on multiple ports and IP addresses,
one of each per user,
with the concomitant confusion of configuration files.
On the other hand, if the server is to be allowed to change its identity,
it needs to be done in a controlled manner, so that the chance
of compromising your system's security is kept to a minimum. (Remember,
Apache is usually started as root and only changes
to nobody later!)
The suexec (pronounced 'SUE-ex-Ek')
tool helps make this possible. It's found in the
src/support/ directory under your Apache source tree.
Assumptions in This Article
For the rest of this article, I'm going to make
the following assumptions:
- your Apache source tree starts at
./apache-1.3/
- your Apache ServerRoot is
/usr/local/web/apache
- your Apache DocumentRoot is
/usr/local/web/htdocs
- the username under which Apache runs (the value of the
User directive in your httpd.conf file)
is nobody
All of the cd and other shell commands in this article
that refer to directories use these locations.
How Does Suexec Work?
Since suexec works by wrapping an operation up in
a package executed under a different username, it's called a
wrapper. In order to execute a script under the auspices of
the wrapper, the Apache server creates a child process running
the suexec binary and passes the particulars to it.
The wrapper verifies that all the security requirements are
met, edits the list of environment variables so that only the
ones on its 'trusted' list are available, closes its logfile,
and calls some flavour of execv(2) to load the script into the
edited process environment, replacing suexec itself.
Requirements For suexec Operation
Since suexec is used to run applications on your
system on behalf of arbitrary people out on the Web, it's very
paranoid about doing anything that might compromise your system's
security. Here is a list of the conditions suexec
requires to be met before it will proceed; if any don't measure up, the
wrapper will log an error and not execute the script.
suexec must be invoked with the correct number of
arguments. If it isn't, it assumes someone is trying to penetrate
your system by running it outside the Apache environment.
- The username/UID invoking
suexec must be a valid user;
that is, it must be listed in the /etc/passwd file.
If it isn't, something's not quite right--and when in doubt,
punt.
- The username executing the wrapper must be the one that was
compiled into it when it was built. Again, a mismatch here is
interpreted as someone trying to use
suexec in other
than the prescribed way.
- The requested script must be a valid Web-space reference
relative to the user's directory or the DocumentRoot;
it cannot be an absolute filesystem path (i.e.,
it cannot start with a "
/") and cannot include
any up-level references (i.e., no "../"
references either).
- The username and group under which the script is to be run
must be valid, cannot be '
root', and must be above
the minimum UID and GID values (set with the --suexecuidmin
and --suexec-gidmin options to the configure
script, which both default to 100). In addition, the group must
be a valid name, and not just a numeric GID.
- The wrapper must be able to change its idenity to the
requested username and group.
- The script (and obviously the directory in which it lives)
must actually exist and the wrapper must be able to
chdir() to the directory.
- If the script isn't from a
~username request,
the script directory must be under the directory specified by
DOC_ROOT (defined by the --suexec-docroot
option to configure).
- The permissions on the specified script and its parent
directory must not allow write access to either the
group
or the other categories.
- The script file cannot be setuid or setgid.
- The script and the directory must be owned by the
user and group as which it is to be executed.
- The script must be executable by the user.
suexec must be able to allocate memory in which to
reproduce the environment variable list.
As you can see, the requirements for execution are pretty stringent.
The sheer number of things that can go wrong argues for the use of the
wrapper only when it's really necessary.
Enabling suexec
The suexec wrapper isn't turned on or off by any
particular Apache directive setting. Instead, when the Apache server
is compiled, one of the constants set (SUEXEC_BIN)
is a string pointing to the location of the suexec
binary. When the server starts, it looks for the binary at that
location; if it's found, suexec is enabled--not otherwise.
This is very important.
This means that even a normal Apache build that was performed
without any thought given to using the wrapper can suddenly
become suexec-enabled if a properly protected
suexec binary is put into place between server restarts.
In the master sources, the default value of SUEXEC_BIN
is set to "$HTTPD_ROOT/sbin/suexec"; the default value of
HTTPD_ROOT is platform-specific:
| Platform |
Default value of HTTPD_ROOT |
Resulting default SUEXEC_BIN value |
| OS/2 |
/os2httpd
| /os2httpd/sbin/suexec |
| Windows |
/apache |
/apache/sbin/suexec |
| BeOS |
/boot/home/apache |
/boot/home/apache/sbin/suexec |
| Novell NetWare |
sys:/apache |
sys:/apache/sbin/suexec |
| All others |
/usr/local/apache |
/usr/local/apache/sbin/suexec |
You may change the values of either--or both--of the
HTTPD_ROOT and SUEXEC_BIN constants
when you recompile the Apache server.
If Apache does find the wrapper, it reports it in
the server error log like this:
[Thu Dec 30 01:24:43 1999] [notice] suEXEC mechanism enabled
(wrapper: /usr/local/web/apache/bin/suexec)
Up until Apache version 1.3.11, there was no way to be sure where a
compiled Apache server is going to be looking for the suexec
binary. As of 1.3.11, though, it's part of the 'compiled modules'
report displayed by the '-l' switch:
% /usr/local/web/apache/bin/httpd -l
Compiled-in modules:
http_core.c
mod_so.c
suexec: enabled; valid wrapper /usr/local/web/apache/bin/suexec
The 'enabled; valid' notation means that the
wrapper is actually present in the indicated location, and
the permissions are correct. If the wrapper isn't there,
or the permissions are wrong, the output will indicate that
suexec is disabled.
Compiling Suexec
Because most of suexec's control parameters are
defined at compile-time, the only way to change them is to
recompile. And since the wrapper works very closely
with the Apache Web server--to the point of both applications
having to share some compile-time definitions--the way to
recompile suexec is to recompile all of Apache.
If you've never done this before, you can see a brief
treatment of the process in the "Building Apache at Lightspeed" section of this article.
There are several suexec-specific options to the
apache-1.3/configure script. Here they are:
--enable-suexec
- The presence of this option on the command line simply
informs the
configure script that you want
the wrapper to be built as well. Without this option,
suexec will not be built, even if there are
other suexec options on the command line.
--suexec-caller=username
- This must be the username under which your Apache
server runs; that is, the one specified on the
User
directive outside all <VirtualHost>
containers. If suexec is invoked by any other
user, it assumes it's some sort of probing attempt and fails
to execute (after logging the user mismatch).
The default username is www.
--suexec-docroot=path
- This specifies the ancestor directory under which all CGI
scripts need to reside in order to be acceptable to
suexec.
(This restriction doesn't apply to scripts activated by
~username-style URLs.) If you have multiple
virtual hosts using suexec, their DocumentRoots
(if you're using .cgi files)
must all be located somewhere in the hierarchy under this
directory, or else the wrapper will assume someone is trying
to execute something unexpected and will log it as an
intrusion attempt. ScriptAliased
directories must be under this hierarchy as well, and this
is in fact more important for them since they
commonly aren't under the DocumentRoot.
The default value for this option is PREFIX/share/htdocs,
where 'PREFIX' comes from the value of the --prefix
option, explicit or implied.
--suexec-gidmin=gid
- Another one of
suexec's restrictions is that
the user it's being asked to execute the script as mustn't
be considered 'privileged.' On Linux and other Unix-like
systems this generally
means that it mustn't be the root user, but
suexec takes this a step further and will refuse
to execute as any user with a group ID less than the value of
this option.
The default value for this option, if not specified, is 100.
--suexec-logfile=filename
-
This specifies the name of the file to which the wrapper will report
errors and successful invocations. It is opened and accessed
as
root, but closed before control is passed to the
script.
The default for this option is PREFIX/var/log/suexec_log,
where 'PREFIX' is the value from the --prefix option.
--suexec-safepath=path-list
- Not only is the list of environment variables examined and
sanitized before the script is invoked, but the default
PATH is set to a known list of directories
as well. This list is hard-coded at compile-time, and is
defined by this option.
The default value for --suexec-safepath is
/usr/local/bin:/usr/bin:/bin.
--suexec-uidmin=uid
-
As with the
--suexec-gidmin option described earlier,
this option is used to inform suexec of forbidden
UID values. If a request is made that would result in the
execution of a script by a user with a UID equal to or less than this
value, the wrapper will log the fact and not process the request.
This foils things like a request for ~root/script.
The default value for this option is 100.
--suexec-umask=octal-umask
- This option defines the default permission mode to be
applied to files created by the script (if it doesn't explicitly
set them itself). The umask is specified as a three-digit
octal number indicating which permission bits should not
be set; see the description of the
umask(1)
command for more details.
If this option isn't defined at compile-time, at run-time
the suexec wrapper will inherit the umask setting
from the parent Apache server process.
--suexec-userdir=path
- This option specifies the subdirectory underneath a user's
home directory that
suexec will use to find scripts
for ~username-style URLs. This needs to match the
setting of the UserDir directive in your server
configuration files.
Note: suexec can only handle simple subdirectory
expressions. The more complex pattern-handling capabilities
of the mod_userdir module (which implements the
UserDir directive) cannot be used with the
suexec wrapper.
The default --suexec-userdir setting is
public_html.
If you want to change the location of the suexec
binary, you can do so by adding a new definition of SUEXEC_BIN
to the compilation flags:
% env CFLAGS="-Wall -DSUEXEC_BIN=\"/usr/local/web/apache/suexec\"" \
> ./configure --enable-suexec ...
You should be extremely cautious about changing other definitions, such
as HTTPD_ROOT, however, since suexec isn't
the only part of Apache that uses them.
User IDs Suexec Will Use
Since the point of suexec is to handle certain
Web requests under a different identity than the Apache server user,
there needs to be some way to specify just which user.
There are two places from which Apache will draw this information:
- The username from URLs such as
<URL:http://somehost.com/~username/foo.cgi>,
OR
- The
User and Group directives in the server
configuration file, httpd.conf.
The username to use is determined by checking these in the above order.
The User and Group directives
are ordinarily ignored inside <VirtualHost>
containers, but in a suexec-enabled server they take
on new meaning for the virtual host, defining the identity under
which CGI scripts requested through that host will be executed.
If a virtual host doesn't have a User directive, it
inherits the server-wide value (which defines the username under
which the server itself is running) which will probably result
in normal, non-suexec-enabled behaviour.
Incorporating Suexec Into Your Apache Server
If you have an Apache 1.3 server binary, it's capable of using
a suexec wrapper if it finds one in the expected
place. (Until Apache 1.3.11, there was no convenient way to find out what
the 'expected place' is; as of version 1.3.11, you can find
out the value of the SUEXEC_BIN compile-time constant,
and whether there's a valid wrapper at that location, with the
'httpd -l' runtime switch.)
If you're working with an Apache server that you inherited, or
installed as part of a package, you might not be sure whether
suexec is in place or being used. If you want to
be sure about it, the best thing to do is to use the
Apache build procedure, which will dot the Is and cross the Ts
when you 'make install'.
The main mechanism suexec uses to ensure safety is
to rely on a bunch of settings made at compile-time. Likewise, the
only way Apache can be made to even think about using
suexec is it if has been compiled with that in mind.
This means that you'll probably need to compile both the
Apache server and suexec yourself.
This is easily done as part of the normal Apache build. Just use
the following command and the rest is easy:
% cd ./apache-1.3/
% ./configure \
> --enable-shared=max \
> --enable-module=most \
> --with-layout=Apache \
> --prefix=/usr/local/web/apache \
> --with-port=80 \
> --suexec-enable \
> --suexec-caller=nobody \
> --suexec-docroot=/usr/local/web
- Note:
- The Red Hat 6.1 Apache RPM actually installs
suexec
by default, which may cause you problems. If you don't want it,
you'll need to either rebuild Apache or disable the suexec
execution.
Disabling Suexec
If your Apache installation is currently suexec-enabled,
it's very simple to turn the wrapper off. Just do one or more
of the following to the suexec binary:
- Clear the
setuid bit
- Change the owner to be someone other than
root
- Delete or rename it
and then restart the Apache server.
Doing any one of these will render the suexec
facility unusable, and Apache won't even try to involve it.
To verify that your action has had the desired effect, verify
(if you're running Apache 1.3.11 or later) with the
"/usr/local/web/apache/bin/httpd -l" command.
If the output says suexec is enabled, you haven't
done enough yet.
Testing Your Installation
The simplest way to verify that suexec is
functioning properly is to install a script that will tell
you the username under which it's being invoked.
# cd /usr/local/web/apache/cgi-bin/
# cat > showuser.cgi << EOS
#!/bin/sh
echo "Content-type: text/plain"
echo ""
echo "Username="`whoami`
EOS
# chmod 755 showuser.cgi
# chown user1.group1 . ./showuser.cgi
(By calling it "showuser.cgi" you can copy it directly
into a user's directory without having to rename it.
Filename extensions on scripts in ScriptAliased
directories are ignored, so it does no harm to keep the
.cgi extension.)
Note that the cgi-bin/ directory isn't under the
DocumentRoot, which is why the --suexec-docroot value
was bumped up one level--that way it covers both the ServerRoot
(including the cgi-bin/ directory) and the DocumentRoot.
Since there are two ways in which suexec can be invoked,
you should test both of them:
- Server-wide
suexecution
- First, create a
<VirtualHost> container
(or use an existing one) in your server configuration files, and
add User and Group directives to it.
Pick some username and group that are different from the
normal server user. Next, make sure that you have a
ScriptAlias directive that points to the
directory where you put your test script. Next, make sure
that the cgi-bin/ directory and the test script
are owned by the user and group you've chosen, and are mode
755. Finally, (re)start the Apache server and request the
test script with some URL like
<URL:http://myvirtualhost/cgi-bin/showuser.cgi>.
If you get an error, examine the server error log and the
suexec log.
- User directory
suexecution
- To test that
suexec will properly handle a
CGI script in a user's directory, copy your showuser.cgi
script into that user's public_html/ directory,
make sure that both the script and the public_html/
directory itself are mode 755 and owned by the user, and then
request the script with a URL such as
<URL:http://myhost/~user/showuser.cgi>.
If you get an error page, look at the Apache and suexec
logs.
Debugging
Debugging a suexec problem can be frustrating, particularly
since almost any problem with a CGI script in a
suexec-enabled environment turns out to be related
to the wrapper.
The typical warning signal of a suexec problem is a
request for a CGI script that results in a '500 Internal Server Error'
page. The appropriate response behaviour to such an error is to
look in the server's error log. Unfortunately, because the
wrapper is applying its own restrictions and rules on the script,
the server log may be quite unrevealing, containing only a single
line such as the following for the failed request:
[Sun Dec 26 20:02:55 1999] [error] [client n.n.n.n]
Premature end of script headers: script
The real error message will be found in your suexec
log (which is located at
/usr/local/web/apache/logs/suexec_log, according to
the assumptions section of this article).
The suexec error message may look like this:
[1999-12-26 20:02:55]: uid: (user/user) gid: (group/group) cmd: test.cgi
[1999-12-26 20:02:55]: command not in docroot (/home/user/public_html/test.cgi
Here are a couple of other common suexec error messages:
directory is writable by others: (path)
target uid/gid (uid-1/gid-1) mismatch with
directory (uid-2/gid-2) or program
(uid-3/gid-3)
If it's still not clear what's going wrong, review the list of
requirements and make sure they're all being met.
"Danger, Will Robinson!"
When you suexec-enable your Apache Web server, a lot
of behaviours change:
- CGI scripts in
ScriptAliased directories will
be executed under the identity of the username specified in the
User and Group directives
- CGI scripts in user directories (as specified by the
USERDIR_SUFFIX definition, set by the
--suexec-userdir option) will be executed as the
owning user if and only if
- the script was requested using the
~username
syntax, and
- all of the ownership and permission requirements are met
If the ~username URL format is used but the
permissions/ownerships aren't correct, the result will be
a '500 Internal Server Error' page, not the script being
executed by the server user as in a non-suexec
environment
- CGI scripts in all user directories accessed through
~username URLs will go through the
suexec process--even those that you didn't
consider or expect.
One effect of these changes is that previously-functioning
user scripts may suddenly begin to fail, giving the visitor
the fatal '500 Internal Server Error' page, and giving you,
the Webmaster, an unrevealing "Premature end of script
headers" message in the server error log. This is where it
becomes easy to get frustrated by simply forgetting to check
the suexec error log.
Another aspect of the use of suexec is that, if you
have virtual hosts with different User or Group
values, they cannot share ScriptAliased directories--because
one of the requirements is that the script and the directory
must be owned by the user and group suexec is being told
to use. So you may have to duplicate a lot of your cgi-bin/
stuff into per-vhost directories that are owned and protected
appropriately.
Frequently Asked Suexec Questions
The suexec wrapper isn't perfect, and some aspects
of its design result in it being less than ideally suited
to all environments. Here are some of the more common questions, changes,
and enhancements that come up again and again:
- Q:
- The single
--suexec-docroot value is
irksome. I have 50 virtual hosts with DocumentRoot
values like /vhost1, /vhost2, and
so on. The only way I can get suexec to work
with these is to use --suexec-docroot=/, which
hardly seems secure.
- A:
- This is unfortunately the way it is with the
suexec
that comes with Apache up through version 1.3.11. The value
you specify for --suexec-docroot must
be an ancestor of all of the non-~username
documents that use it. This restriction may be lifted
in a future version, but even then it would require
settings specified at compile-time, such as with something like
--suexec-docroot=/vhost1,/vhost2.
- Q:
- I only want
suexec to be used in certain
directories or user accounts.
- A:
- As of Apache 1.3.11,
suexec is
an all-or-nothing proposition. If it's available and enabled,
it will be used in all cases when a CGI script is invoked.
A future version of Apache may provide a means of controlling
this with greater granularity.
- Q:
- Why don't the Apache CGI error messages say there's a
problem with
suexec?
- A:
- Because Apache really doesn't know that for a fact.
All it knows is that called an internal function to invoke
the CGI, and the interaction with the script failed as
described in the error message. The error might have
been caused by a failure to meet
suexec's
requirements, or it may have been the result of a
bona fide error in the script itself.
- Q:
- Why aren't
suexec's error messages logged
in the Apache server log?
- A:
- In order for the messages from
suexec to
appear in the main server's log, they would have to actually
be passed to Apache so that Apache did the logging. Not only
is this inappropriate for the Web server to do, but there
would be additional confusion about into which
error log the messages should go.
Going Further
There are a few articles on the Web about working with the
suexec wrapper. Don't neglect the man
page included with the source; you can view it directly with
% cd ./apache-1.3/src/support/
% man ./suexec.8
You can also find some documentation at the following URLs:
In Conclusion
The suexec application is a double-edged sword.
It allows you to execute scripts under other personæ
than the basic server user--but it can also cut you unexpectedly
if you're not careful. A single misconfiguration can break
all of your CGI scripts, so consider and plan carefully,
and test thoroughly, before implementing the wrapper on your
production systems.
Acknowledgements
I would like to thank someone named "ryan" for pointing out a
typo in the code examples in my
Adding PHP to Apache on Linux article
in December 1999. The report came very soon after publication
and the error was corrected immediately, so very few readers
saw the broken code. Thanks, ryan!
Got a Topic You Want Covered?
If you have a particular Apache-related topic that you'd like covered
in a future article in this column, please let me know; drop me
an email at
<coar@Apache.Org>.
I do read and answer my email, usually within a few hours
(although a few days may pass if I'm travelling or my mail volume is
way up). If I don't respond within what seems to be a reasonable
amount of time, feel free to ping me again.
About the Author
Ken Coar
is a member of the Apache Group and a director and vice
president of the
Apache Software Foundation.
He is also a core member of the
Jikes open-source Java compiler project, a contributor to the
PHP project, the author of
Apache Server for Dummies, and a contributing
author to
Apache Server Unleashed.
He can be reached via email at
<coar@apache.org>.
Appendix: Building Apache at Lightspeed
|
If you need to build Apache from source in order to add or change
the suexec parameters, you can use the following
commands as a quick-start.
You should download the latest released version of the Apache
tarball and unpack it into a working directory. The top-level
directory will then be ./apache-1.3, which matches
assumption #1 described earlier.
% cd ./apache-1.3
% env CC=gcc CFLAGS="-O2 -Wall" \
> ./configure --enable-shared=max --enable-module=most \
> --with-layout=Apache --prefix=/usr/local/web/apache \
> --with-port=80 \
> --enable-suexec \
> --suexec-caller=nobody \
> --suexec-docroot=/usr/local/web \
> --suexec-umask=022
Configuring for Apache, Version 1.3.12-dev
+ using installation path layout: Apache (config.layout)
+ Warning: You have enabled the suEXEC feature. Be aware that you need
+ root privileges to complete the final installation step.
Creating Makefile
Creating Configuration.apaci in src
[more configuration output]
% make
[lots of compilation output]
% make install
[lots more output describing file placement]
% /usr/local/web/apache/bin/apachectl start
If you didn't encounter any errors, you should now have a
working Apache installation in the location that matches
assumption #2 described earlier.
It's been built to include suexec, and you
should verify that this is the case:
% /usr/local/web/apache/bin/httpd -l
Compiled-in modules:
http_core.c
mod_so.c
suexec: enabled; valid wrapper /usr/local/web/apache/bin/suexec
It's far beyond the scope of this
article to give any more information about building Apache.
If you'd like to see an article in this column about the details
of building Apache, let me know.
|
Copyright Jupitermedia Corp.
All Rights Reserved.
|