When you have a multiuser system running a web server, standard practice for CGI and PHP support is to have the server run these scripts as a global user, e.g. www-data. That's braindead. Your script can't keep any data secret, nor can it write any data anywhere without opening that data up to sabotage by another user. Another user on the system may write their own script which, running as that same global user, will read your secrets or trash your saved state.
Part of the problem is that people are obsessed about performance. Industry standard practice is to link PHP into Apache directly, so that Apache doesn't need to so much as fork in order to run a PHP script. Unless you're setting up an oversold shared web server, your performance bottlenecks are going to be the network or within the scripts themselves. I doubt much is to be gained by linking your scripting language implementation directly into the web server. This practice runs against the goal of ensuring each script is run by the user that owns it.
There are answers. FastCGI is an enhancement to CGI to add support for persistent processes, so that bulky interpreters and all their libraries don't have to be loaded each and every time a dynamic page needs to be calculated and sent to a user. suEXEC is a mechanism to execute CGI scripts as the user who owns them.
Some solutions to this problem involve traditional suEXEC/CGI execution of PHP scripts. If you don't mind the (alleged) performance loss of CGI to FastCGI, that's okay. Except that you have to ensure each PHP script is a proper executable on your server: modify the file mode to allow execution, and add a shebang to the top of the script. Some PHP software makes it hard to tell which ".php" files are scripts for end-user consumption, and which are "includes." So, another one of my goals is to avoid having to do any of that. Leave PHP files as 0644, with no shebang.
Apache configuration is opaque and complicated. I'd love to explain in abstract terms what you should do to enable a sane configuration, but I'll have to settle for posting a recipe. My web server runs Apache 2 and PHP 5 on Debian Etch; paths and configuration conventions for Debian are assumed.
- For Debian systems, ensure the following packages are installed:
- apache2: You may opt for the "worker" MPM, "apache2-mpm-worker", since PHP scripts will be run in their own separate processes.
- php5-cgi: Includes FastCGI support.
- libapache2-mod-fastcgi
- Modify the following configuration files:
-
/etc/apache2/mods-available/userdir.confEnable CGI and FastCGI for users by adding "
ExecCGI" to theOptionsdirective:<IfModule mod_userdir.c> ... <Directory /home/*/public_html> ... #Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec ExecCGI ... </Directory> </IfModule>
-
/etc/apache2/mods-available/fastcgi.confFastCGI needs to run user scripts through suEXEC. We can enable suEXEC in this file by specifying "
on" for theFastCgiWrapperdirective. —HOWEVER— if you do that, you cannot run non-user FastCGI scripts. At all. Ever. If the script is owned by root and lives in/var/www/default, suEXEC will throw a hissy fit over trying to run a script as root. If you useSuexecUserGroupto specify that non-user scripts are run as www-data, suEXEC will throw a similar hissy-fit. The best part is, you can't have differentFastCgiWrapperdirectives in different scopes, so that you might enable the wrapper only for userdirs.To work around this mess, we create a trivial hack script and tell FastCGI to always use this.
<IfModule mod_fastcgi.c> ... FastCgiWrapper /usr/local/sbin/fastcgi-suexec-hack ... </IfModule>
-
-
Create our FastCGI wrapper hack script:
/usr/local/sbin/fastcgi-suexec-hack#!/bin/sh # This hack exists exclusively to work around the restriction that # FastCGI wrappers (e.g. suEXEC) are an all-or-nothing ordeal. Thou # shalt not enable wrappers for userdirs but not for the whole site. # Thou shalt not configure non-userdir FastCGI scripts to use suEXEC # or thou shall suffer my wrath of mysterious suexec policy violation # notices for 7 generations. username="$1" group="$2" application="$3" case "$(pwd)/" in /home/*/public_html/*) exec /usr/lib/apache2/suexec "$username" "$group" "$application";; *) application_abs="$(readlink -f "$application")" exec "$application_abs";; esac
Make it executable:
sudo chmod 755 /usr/local/sbin/fastcgi-suexec-hack -
Enable the following modules: userdir, suexec, actions, fastcgi
$ cd /etc/apache2/mods-enabled
$ sudo ln -s ../mods-available/{userdir,suexec,actions,fastcgi}.{load,conf} ./ (Ignoring errors about missing
.conffiles.) -
Create a new configuration file:
/etc/apache2/conf.d/php-fastcgiAddType application/x-httpd-php-fastcgi .php Action application/x-httpd-php-fastcgi /cgi-bin/php-fastcgi.fcgi
This bit tells apache to run
/usr/lib/cgi-bin/php-fastcgi.fcgifor all PHP scripts, without the need to make them executable or add a shebang line. -
Create our PHP/FastCGI wrapper script:
/usr/lib/cgi-bin/php-fastcgi.fcgi#!/bin/sh PHP_FCGI_CHILDREN=4 PHP_FCGI_MAX_REQUESTS=5000 export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS exec php-cgi
Make it executable:
sudo chmod 755 /usr/lib/cgi-bin/php-fastcgi.fcgi -
For each user:
- Copy the
php-fastcgi.fcgiscript to theirpublic_htmldirectory. It cannot be a symlink, because of suEXEC paranoia checks. -
Add the following line to the "
.htaccess" file in theirpublic_htmldirectory, to direct PHP scripts to their copy of the wrapper:# Required to ensure suEXEC runs PHP scripts under our user ID. Action application/x-httpd-php-fastcgi /~username/php-fastcgi.cgi
Unfortunately, since this includes the user's username, this can't be included in an
/etc/skelfile. You could put it there as a template for the user (or you) to change after an account is created, though.
- Copy the
-
Create a test script to ensure PHP scripts are running under the correct user ID. Place it in
/var/www/default/test-id.phpand~/public_html/test-id.php. The latter copy should probably be owned by you.<?php header ("Content-Type: text/plain"); system ("id"); ?>Recall that you should not be required to make these files executable, or to add shebang lines at the top.
- Test it! After restarting Apache, try /test-id.php and /~you/test-id.php. Respectively, you should see
uid=33(www-data) gid=33(www-data) groups=33(www-data)and (e.g.)uid=1000(piranha) gid=1000(piranha) groups=1000(piranha)
Questions? Is this broken for you? Chances are I forgot something bone-headedly simple, so get in touch.
References:
