README.md 4.31 KB
Newer Older
ale's avatar
ale committed
1
2
3
4
5
6
7
suexec-sandbox
==============

A slightly improved (and massively simplified) *suexec* wrapper for
Apache [mod_suexec](https://httpd.apache.org/docs/2.4/suexec.html).

It's meant for a very specific and narrow use case: multi-tenant large
ale's avatar
ale committed
8
9
10
scale website hosting of PHP websites using FastCGI. It drops some of
the standard *suexec* features that we weren't using anyway (namely,
user home directory support).
ale's avatar
ale committed
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

It also gets rid of the somewhat arbitrary assumption made by the
default Apache suexec wrapper on document root layout, in favor of a
more explicit and configuration-based approach.

# Usage

Basic usage is simply what you would expect of a *suexec* wrapper:
dump it in the right place (like /usr/lib/apache2/suexec), configure
*mod_suexec* and let Apache invoke it.

There are some differences in behavior with respect to the default
wrapper, and it's unlikely that *suexec-sandbox* will work out of the
box. Let's see what they are.

## Configuration

The suexec-sandbox wrapper requires a configuration file for its
operation. Its location is hard-coded to
`/etc/apache2/suexec-sandbox.conf`. The configuration file consists of
a series of directives, one per line. Empty lines are ignored, as are
comments (introduced by the *#* character). Directives consist of a
name and a value, separated by a space.

Known directives:

`path` - sets the safe PATH for the wrapped program environment
(default: /bin:/usr/bin).

`allowed_cmd` - add the value to the list of allowed commands (can be
specified multiple times).

`docroot` - add the value to the list of allowed DocumentRoot prefixes
(can be specified multiple times).

`min_uid` - set the minimum UID limit

`min_gid` - set the minimum GID limit

## NSS

The wrapper does not use libnss or interact with the user/group
databases in any way, so you **MUST** use numeric UIDs and GIDs in
your Apache configuration.

ale's avatar
ale committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# Usage with FastCGI

The overall suexec workflow is the following: upon receiving a request
for a suexec-wrapped CGI, Apache will:

* cd to the directory containing the file, which will be somewhere
  below the DocumentRoot of the site;
* launch the */usr/lib/apache2/suexec* wrapper with the arguments
  *uid* *gid* *file*.

Here *uid* and *gid* will be the user and group ID specified in the
SuexecUserGroup *mod_suexec* configuration directive.

In this case, the standard suexec wrapper will perform a number of
safety checks, including verifying that the file to execute is indeed
owned by the specified user and group.

In a FastCGI setting, which we're more interested in, things are
slightly different: when using FastCGI, suexec does not get invoked on
every request, but it is only used to start the FastCGI process, a
long-running server that will handle many requests. Therefore, the
file being executed isn't the target PHP file, but rather the PHP
interpreter itself (e.g. */usr/lib/cgi-bin/php5.fcgi*), so it is not
going to be owned by the target user and it wouldn't make sense to
perform ownership checks. Furthermore, the directory suexec will be
invoked from will always be */usr/lib/cgi-bin*, losing any relation
with the site's document root.

Combining suexec and FastCGI is thus less effective from a security
standpoint, as many checks are unavailable and we're forced to rely
more on UNIX-level permissions and features. On the other hand, since
FastCGI processes are long-lived, setup latency isn't as critical
anymore, and we can do more complex initialization of the process
space.

## Checks

The wrapper performs the following safety checks:

* it fetches the full path of the command using realpath(3)
* the command is checked against a list of allowed commands
* the current directory is checked against a list of allowed path
  prefixes
* the command has the current directory as its prefix
* *uid* is greater than `min_uid`
* *gid* is greater than `min_gid`

If the checks pass successfully, the wrapper will:

* spawn a child in an isolated user namespace, with its own usermap,
  process space, etc. and optionally an entirely different filesystem
  root (similarly to a chroot);
* filter out dangerous system calls with *seccomp*
* drop all privileges;
* switch to the specified *uid* and *gid*
* execute the target command

The namespace isolation is lifted, with minor modifications, from
https://blog.lizzie.io/linux-containers-in-500-loc.html so you can
check that page for the gory details.