----------
OVERVIEW
----------
ExecWrap is a super-user exec wrapper for the lighttpd web-server, but it can be used in
any environment as long as arguments can be passed from the server to its children via the
environment.
ExecWrap is released under the BSD license, which may be found in the source file.
The different compile-time configuration options are explained below. For optimal security,
please review ALL of them, as default values are hard to provide in most cases. The
configuration is mainly concerned with security. Next, the run-time configuration options
are explained.
An overview of the security implications of each check the wrapper performs is then given,
along with some checks it doesn't perform and the reason for it.
Finally, the run-time configuration is explained and a usage example is given.
-------------------
COMMAND ARGUMENTS
-------------------
The super user and the user specified by the PARENT_UID parameter (see below) are allowed
to give command line options to the wrapper. These are only useful for humans, and not for
a web-server.
Argument Explanation
-------------------------------------------------------------------------------------------
-v Displays the name, i.e. ExecWrap, and version of the wrapper.
-V Displays name, version and the compile-time configuration parameters.
----------------------------
COMPILE-TIME CONFIGURATION
----------------------------
After compiling, remember that the program must be owned by the super user and have its
setuid-user bit set to work! The configuration parameters are found in the file
execwrap_config.h, except for the ones that shouldn't normally be changed. Those are found
in the main file.
Config parameter Explanation
-------------------------------------------------------------------------------------------
PARENT_UID The (numeric only) value of this parameter controls what parent UID
the wrapper will accept. If it is started by any other UID than this
or the super user, it will instantly abort.
TARGET_MIN_UID This (numeric) value sets the _minimum_ UID the wrapper can be told
to switch to. If asked to switch to a lower value, it will abort.
It is _strongly_ advised to set this value to the _minimum_ needed.
Setting it to 0 would allow the server to become root, wherefore it
is disallowed (by compile-time security checks).
TARGET_MIN_GID The same as TARGET_MIN_UID, but for the GID. The same reasons and
restrictions apply.
TARGET_PATH_PREFIX This parameter sets the prefix of any target program the wrapper is
instructed to invoke. If asked to start something _not_ under this
path, the wrapper aborts.
DEFAULT_UID The default (numeric) UID to become, if no UID is given via the
environment. It defaults to 65534, which usually maps to the nobody
user. This is recommended. At any rate, it must be at least as high
as TARGET_MIN_UID. This is ensured by compile time security checks.
DEFAULT_GID The same as DEFAULT_UID, but for the GID. The same reasons and
restrictions apply.
The following configuration parameters shouldn't normally be changed. In case you have a
clash with some other ENV arguments to the target program, they can be changed, however.
The configuration dump command line option, -V, will not output them.
ENV_UID This parameter controls the name of the environment variable used to
tell the wrapper what UID to switch to. It defaults to "UID=" and
there is usually no need to change it. If you do, remember that it
must end in "=" like the default, or it will fail to be recognised.
See the run-time configuration section below.
ENV_GID The same as ENV_UID, but controls the name of the GID parameter.
Defaults to "GID=".
ENV_TARGET The same as ENV_UID, but controls the name of the TARGET parameter.
Defaults to "TARGET=".
ENV_CHECK_GID The same as ENV_UID, but controls the name of the CHECK_GID
parameter. Defaults to "CHECK_GID=".
ENV_NON_RESIDENT The same as ENV_UID, but controls the name of the NON_RESIDENT
parameter. Defaults to "NON_RESIDENT=".
---------------------
COMPILE AND INSTALL
---------------------
There is no Makefile right now, but compile is as simple as:
> gcc -O2 -o execwrap execwrap.c && strip execwrap
Or similar, depending on taste. You need something other than an ancient compiler to make
it work, preferably C99. To install, make sure the file is owned by the super user and has
the proper modes:
> chown root execwrap
> chmod og-w,+rx,u+s execwrap
Then move it to where it's supposed to reside. That's all. If you're really fanatic about
security, you can also do something like this (before the chmod u+s):
> chgrp lighttpd execwrap # <-- Or whatever group your web-server is in.
> chmod o-rwx,g-rw execwrap
To make the command impossible to start for anyone but the super user and the server admin.
But since the first thing the wrapper does on start up is to check the parent UID, this
really isn't necessary.
------------------------
RUN TIME CONFIGURATION
------------------------
The run-time configuration is provided by environment variables, and not the command line.
Currently, command line arguments are completely ignored. The run-time arguments are
subjected to the restrictions imposed by the compile-time configuration discussed above.
Note that since the names of the parameters can be changed, the defaults are used in the
following list.
Config parameter Explanation
-------------------------------------------------------------------------------------------
UID The UID to switch to. Only numerical values are accepted currently.
GID The GID to switch to. Only numerical values are accepted currently.
TARGET The target program to start. For security, it must be absolute and
must not contain any ~ characters or ".." sub-strings. Of course the
compiled-in prefix must also be a prefix of it.
CHECK_GID If set (to anything, even the empty string), the security checks will
be slightly relaxed to allow targets owned by the target GID but not
necessarily by the target UID, as well as allowing the target to be
group-writable if owned by the target GID. Useful for projects where
several people collaborate so file ownership can vary.
NON_RESIDENT If set (to anything), the wrapper will drop privileges and become the
target process directly, instead of the default behaviour where it
forks off before becoming the target, allowing SIGTERM to propagate
from the caller of the wrapper, to the target. It is not recommended
to set this, as it will make it impossible for the caller (usually a
web-server) to terminate the target process, and thus prevents it
from effectively managing it.
-----------------------------------
SECURITY CHECKS AND THEIR REASONS
-----------------------------------
The security checks below are listed in the order they are performed.
Security check Reason it's performed
-------------------------------------------------------------------------------------------
Check parent UID Done to disallow people other than root and server admins to become
another user. The reason for this should be obvious, but given all
the other checks, this one actually isn't the most important one.
Check min. UID/GID Done to control what users the server admins can start programs as.
In particular, they can't start programs as root. Again, the reason
for checking this should be obvious. Never ever set these values to
anything at or below system services users and groups!
Target path checks We check that it's absolute and doesn't contain ~ and "..". This is
an overly careful (but also simpler) way to make sure it doesn't
lead outside of the prefix.
Target path prefix Done to prevent server admins from executing user code outside the
designated server root. This is not in itself a security enhancement
unless the system in general is kept in a tight leash, but it also
helps prevent a run-away server from causing problems.
Target given Done. This isn't for security, but without a target there is no work
to be done in the first place :-p.
Can switch UID/GID Done. Again, this is usually not a security enhancement, as the
switch should never fail. In certain environments however, additional
security measures such as MAC might prevent even the super user from
switching to certain UIDs or GIDs.
Can stat the target Done. Otherwise, we can hardly expect to become the target anyway.
Not for security as such.
Target has wrong Done to prevent users not owning the target file, or not in the same
modes or UID/GID group, from replacing it with malicious code. The following checks
are performed. If any of them fails, the wrapper aborts.
1. Target is NOT world-writable.
2. Target is owned by the UID we switched to OR, when CHECK_GID is
given, target is owned by the GID we switched to.
3. Target is NOT group-writable unless CHECK_GID was given and it
is owned by the GID we switched to.
Also, there is one common check we do not perform:
Target dir checks Not done. Checking the target directory adds no real security. True,
if someone else has write permissions he can remove the target and
replace it with something else, but this won't have the correct UID
and GID. If he can make it have the right values, he would be able to
overwrite the target in the first place. Care must be taken here if
the CHECK_GID feature is used along with the setgid bit. See below.
-------------------------------
OTHER SECURITY CONSIDERATIONS
-------------------------------
Even though several steps are taken to prevent abuse, care must be taken when setting up
any system involving a super-user executable. In particular, consider the following:
1. Make sure the wrapper is safe. It should at most be user-writable.
2. Make sure the group setup for the web server is sane. If the setgid bit is enabled for
the web root, which is useful in many cases and is the reason for the CHECK_GID
feature, make SURE that ALL members of the group in question are accounted for, since
they might be able to replace files in any directory at and below the web root.
-----------------------
CONFIGURATION EXAMPLE
-----------------------
Compile-time configuration (the <- parts are comments, obviously):
-------------------------------------------------------------------------------------------
PARENT_UID 104 <- My lighttpd UID.
TARGET_MIN_UID 1000 <- The first regular user UID.
TARGET_MIN_GID 100 <- Ditto for group.
TARGET_PATH_PREFIX "/var/www/light/" <- Site web-roots are below this.
DEFAULT_UID 65534 <- Nobody.
DEFAULT_GID 65534 <- Nobody.
Run-time configuration in lighttpd:
-------------------------------------------------------------------------------------------
fastcgi.server = (
"/dispatch.fcg" =>
( "dispatcher" =>
(
"socket" => "/tmp/fcgi.sock",
"bin-path" => "/var/www/light/execwrap", <- The wrapper.
"check-local" => "disable",
"min-procs" => 1,
"max-procs" => 1,
"bin-environment" => (
"UID" => "1000",
"GID" => "407",
"TARGET" => "/var/www/light/blah.tld/html/dispatch.fcg", <- The real script.
"CHECK_GID" => "1"
)
)
)
)