HowToSetupFastCgiIndividualPermissions » History » Revision 33
« Previous |
Revision 33/39
(diff)
| Next »
Anonymous, 2008-04-10 13:18
another limitation
= Setup FastCGI and PHP with individual user permissions =
''First of all: please notice that this how-to is only a suggestion on how to do this, so please don't blame anybody if you prefer to do things differently, or get mad customers, or whatever....''
''Note: This only works on *nix like operating systems. I don't know how to do this on Windows.''
IntroductionRunning a website hosting service for individual users/customers requires some extra brain-work when you set up your web-server.
Basically, you give every user an individual (ordinary) user account on your web-server. The user then uploads her PHP script files to her own virtual host document root.
What we want to do, is to execute all PHP script files with the exact same user permissions as the user that manages the virtual host in question. If this is accomplished, you can be sure that none of your users will be able to browse through other users' PHP scripts.
Consider the following PHP script executed on a web-server without individual user permissions on PHP-scripts (please do not attempt to do this, since you might end up with the police knocking on your door!):
{{{
#!php
$filename = "/path_to_other_users_vhost_root/index.php";
$handle = fopen($filename, "rb");
$contents = fread($handle, filesize($filename));
fclose($handle);
echo $contents;
?>
}}}
This will read (and show) the source code of PHP script of some other user. The source code might contain passwords that gives access to that user's MySQL databases, or other interesting stuff. You could even make a PHP script that writes PHP script files to other user's virtual host directories!
This is the setup we want to get rid of!
What about PHP's built-in safe_modeI will not say any bad things about PHP here, and it is NOT recommended to use PHP's built-in ''safe_mode'' features. (See the [http://www.php.net/manual/en/features.safe-mode.php safe_mode documentation at php.net] for a detailed description.)
There are, however, some php.ini settings that can stop or slow down the most common forms of attack without touching the source code. To stop php's remote access, see [http://www.php.net/manual/en/ref.filesystem.php#ini.allow-url-fopen allow_url_fopen], and to stop php from including remote files you can see [http://www.php.net/manual/en/ref.filesystem.php#ini.allow-url-include allow_url_include]. Setting [http://www.php.net/manual/en/features.safe-mode.php#ini.open-basedir open_basedir] is a good way to slow down an attacker, but is no replacement for user permissions. And to slow down some forms of session stealing, [http://www.php.net/manual/en/ref.session.php#ini.session.use-trans-sid session.use_trans_sid] can be turned off.
It is always best if you rely on your operating system's build-in user permissions.
= Installation =
We assume that you already have Lighttpd installed, and installed PHP with FastCGI support. ([http://trac.lighttpd.net/trac/wiki/TutorialLighttpdAndPHP How to install PHP with FastCGI support])
You need to log in as ''root'' to do this.
1. Add users to the operating system(This is only needed if you haven't added users yet.)
You must add a user account to the operating system for each user that you want to give separate user permissions, in order to deny access to other users' source code.
Let's assume that we need to create three users (fred, george, and ron):
{{{#!ShellExample
- useradd fred
- useradd george
- useradd ron
}}}
You need to add one user group for each user added above. To keep things simple, we just name the user groups similar:
{{{#!ShellExample
- groupadd fred
- groupadd george
- groupadd ron
}}}
Now you need to add users to each of these user groups. For each user group, there must be two members: the corresponding user and the lighttpd daemon user.
You configure the user groups by editing /etc/group with your favourite text editor.
The file must look something like this (group numbers may vary):
{{{
..... [lots of stuff above]
fred:x:441:fred,lighttpd
george:x:442:george,lighttpd
ron:x:443:ron,lighttpd
}}}
You might also use a ''sed'' command like this:
{{{
sed -i "s/^\(fred.*\)$/\1,fred,lighttpd/g" /etc/group
sed -i "s/^\(george.*\)$/\1,george,lighttpd/g" /etc/group
sed -i "s/^\(ron.*\)$/\1,ron,lighttpd/g" /etc/group
}}}
These commands add the user and the lighttpd user to the groups.
3. Set up filesystem structureLet's assume that you want to keep all files associated with the web-server's virtual hosts under the directory ''/var/www''. (Of course you can choose another location, just make sure that the users created above have read and execute rights to the directory. (I.e. ''chmod 755 /var/www && chown root:root /var/www'').
3.1 Create server root directoryNow, create two directories: One for some start-up scripts that only ''root'' have access to, and another for all your virtual hosts:
{{{#!ShellExample
- cd /var/www
- mkdir fastcgi
- mkdir vhosts
- chown lighttpd:lighttpd *
- chmod 755 *
- ls -l /var/www
drwxr-xr-x 2 lighttpd lighttpd 4096 Feb 15 12:17 fastcgi
drwxr-xr-x 9 lighttpd lighttpd 4096 Feb 15 11:21 vhosts
}}}
Now create a directory for each virtual host in the directory ''/var/www/vhosts'', and set up appropriate user rights to them:
{{{#!ShellExample
- cd /var/www/vhosts
- mkdir fred-weasley.com
- mkdir george-weasley.com
- mkdir ron-weasley.com
- chown fred:fred fred-weasley.com
- chown george:george george-weasley.com
- chown ron:ron ron-weasley.com
- chmod 750 *
- ls -l /var/www/vhosts
drwxr-x--- 7 fred fred 4096 Feb 15 20:18 fred-weasley.com
drwxr-x--- 6 george george 4096 Feb 15 11:02 george-weasley.com
drwxr-x--- 6 ron ron 4096 Feb 15 11:23 ron-weasley.com
}}}
Now we have created three directories where the three users cannot see each others' files; however, the ''lighttpd'' daemon user can see it all.
3.3 Create directory structure for each virtual hostNow, we want to create the directory struture needed for each virtual host:
{{{#!ShellExample
- cd /var/www/vhosts/fred-weasley.com
- mkdir html
- mkdir includes (optional)
- mkdir logs
- chown fred:fred *
- chown lighttpd:fred logs
- chmod 750 *
- ls -l /var/www/vhosts/fred-weasley.com
drwxr-x--- 14 fred fred 4096 Feb 17 11:55 html
drwxr-x--- 2 fred fred 4096 Feb 15 12:05 includes
drwxr-x--- 2 lighttpd fred 4096 Feb 15 11:11 logs
}}}
'''You need to repeat this for each virtual host, replacing the user name 'fred' with the appropriate user name.'''
3.4 Create a FastCGI directory for each userNow we have to do all the fun stuff!
Now, go to the ''/var/www/fastcgi'' directory where we want to create a directory for each user. (When we're finished, these directories will hold the sockets to the FastCGI server processes):
{{{#!ShellExample
- cd /var/www/fastcgi
- mkdir fred
- mkdir george
- mkdir ron
- chown fred:fred fred
- chown george:george george
- chown ron:ron ron
- chmod 750 *
- ls -l /var/www/fastcgi
drwxr-x--- 7 fred fred 4096 Feb 15 20:18 fred
drwxr-x--- 6 george george 4096 Feb 15 11:02 george
drwxr-x--- 6 ron ron 4096 Feb 15 11:23 ron
}}}
(Note that the lighttpd user can read all directories, while the three users can only access their own directory.)
4. Create a FastCGI start-up script for each userCreate a directory that will hold all your FastCGI start-up scripts:
{{{#!ShellExample
- cd /var/www/fastcgi
- mkdir startup
- chmod 750 startup
- ls -l /var/www/fastcgi
drwxr-x--- 7 fred fred 4096 Feb 15 20:18 fred
drwxr-x--- 6 george george 4096 Feb 15 11:02 george
drwxr-x--- 6 ron ron 4096 Feb 15 11:23 ron
drwxr-x--- 6 root root 4096 Feb 15 11:23 startup
}}}
Now, go to the ''/var/www/fastcgi/startup'' directory, create a start-up script for ''fred'' (let's call it ''fred-startup.sh'', using your favourite text editor:
{{{
#!sh
#!/bin/sh
- ABSOLUTE path to the spawn-fcgi binary
SPAWNFCGI="/usr/bin/spawn-fcgi"
- ABSOLUTE path to the PHP binary
FCGIPROGRAM="/usr/bin/php-cgi"
- bind to tcp-port on localhost
FCGISOCKET="/var/www/fastcgi/fred/fred.socket"
- uncomment the PHPRC line, if you want to have an extra php.ini for this user
- store your custom php.ini in /var/www/fastcgi/fred/php.ini
- with an custom php.ini you can improve your security
- just set the open_basedir to the users webfolder
- Example: (add this line in you custom php.ini)
- open_basedir = /var/www/vhosts/fred/html
##
#PHPRC="/var/www/fastcgi/fred/"
- number of PHP childs to spawn in addition to the default. Minimum of 2.
- Actual childs = PHP_FCGI_CHILDREN + 1
PHP_FCGI_CHILDREN=5
- number of request server by a single php-process until is will be restarted
PHP_FCGI_MAX_REQUESTS=1000
- IP adresses where PHP should access server connections from
FCGI_WEB_SERVER_ADDRS="127.0.0.1"
- allowed environment variables sperated by spaces
ALLOWED_ENV="PATH USER"
- if this script is run as root switch to the following user
USERID=fred
GROUPID=fred
- no config below this line
if test x$PHP_FCGI_CHILDREN = x; then
PHP_FCGI_CHILDREN=5
fi
export PHP_FCGI_MAX_REQUESTS
export FCGI_WEB_SERVER_ADDRS
export PHPRC
ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS PHPRC"
- copy the allowed environment variables
E=
for i in $ALLOWED_ENV; do
E="$E $i=$(eval echo "\$$i")"
done
- clean environment and set up a new one
env - $E $SPAWNFCGI -s $FCGISOCKET -f $FCGIPROGRAM -u $USERID -g $GROUPID -C $PHP_FCGI_CHILDREN
chmod 770 $FCGISOCKET
}}}
Please be careful with the paths, USERID and GROUPID.
Note that, in this example, the php process runs as the user we created above
('fred'). This means that the php code will have write access to the html and
php files. This can be convenient, but might be a security risk.
Alternatively, you could set USERID to 'nobody' (or any other user without any
specific permissions), to deny write access to the php process.
You need to repeat the process and create a startup-script for each user in the ''/var/www/fastcgi/startup'' directory. (Just copy the file and replace FCGISOCKET, USERID and GROUPID with the correct values).
Remember to set execute permissions on all your startup-scripts:
{{{#!ShellExample
- cd /var/www/fastcgi/startup
- chmod 750 *
}}}
If you're uncertain about the location of your php.ini, just run the following command:
{{{
#!ShellExample
$ php-cgi -i | grep php.ini
}}}
Please check, that you have the following line in your php.ini:
{{{
cgi.fix_pathinfo=1
}}}
If you have uncommented the PHPRC line in the shell script under issue 4., be sure that the php.ini has the correct owner an rights. To get things work this must be
{{{
chmod 644 php.ini
chown root:root php.ini
}}}
Now, fire up all your FastCGI server processes:
{{{#!ShellExample
- /var/www/fastcgi/startup/fred-startup.sh
spawn-fcgi.c.170: child spawned successfully: PID: xxxxx - /var/www/fastcgi/startup/george-startup.sh
spawn-fcgi.c.170: child spawned successfully: PID: xxxxx - /var/www/fastcgi/startup/ron-startup.sh
spawn-fcgi.c.170: child spawned successfully: PID: xxxxx
}}}
If you get any error messages, please re-check your startup-scripts and the permissions to the ''/var/www/fastcgi'' directory, including all user sub-directories.
7. Configure virtual hosts in the lighttpd serverEdit ''/etc/lighttpd.conf'' in your favourite text-editor:
{{{
.....[lots of configuration stuff above].....
$HTTP["host"] =~ "(^|\.)fred-weasley.com$" {
server.document-root = "/var/www/vhosts/fred-weasley.com/html"
accesslog.filename = "/var/www/vhosts/fred-weasley.com/logs/access_log"
fastcgi.server = ( ".php" =>
(
( "socket" => "/var/www/fastcgi/fred/fred.socket",
"broken-scriptfilename" => "enable"
)
)
)
}
$HTTP["host"] =~ "(^|\.)george-weasley.com$" {
server.document-root = "/var/www/vhosts/george-weasley.com/html"
accesslog.filename = "/var/www/vhosts/george-weasley.com/logs/access_log"
fastcgi.server = ( ".php" =>
(
( "socket" => "/var/www/fastcgi/george/george.socket",
"broken-scriptfilename" => "enable"
)
)
)
}
$HTTP["host"] =~ "(^|\.)ron-weasley.com$" {
server.document-root = "/var/www/vhosts/ron-weasley.com/html"
accesslog.filename = "/var/www/vhosts/ron-weasley.com/logs/access_log"
fastcgi.server = ( ".php" =>
(
( "socket" => "/var/www/fastcgi/ron/ron.socket",
"broken-scriptfilename" => "enable"
)
)
)
}
}}}
''Please note the paths to the FastCGI sockets for each virtual host.''
'''server.errorlog is NOT working in conditionals, all errors go to the last logfile specified. So just use one global error log.'''
8. Restart the lighttpd daemon processSimply run this command:
{{{#!ShellExample
- /etc/init.d/lighttpd restart
}}}
If you get any errors, please re-check your ''/etc/lighttpd.conf'' configuration file.
9. Hello World!Now, log in as the user ''fred'' and create a PHP script file in his virtual host (e.g. ''/var/www/vhosts/fred-weasley.com/html/index.php''):
{{{
#!php
echo "<h1>Hello World!</h1>";
echo "<p>Current User ID is: ". posix_getuid();
echo "<p>Current Group ID is: ". posix_getgid();
?>
}}}
Also, make sure to set the file permissions:
{{{#!ShellExample
- chown fred:fred /var/www/vhosts/fred-weasley.com/html/index.php
- chmod 640 /var/www/vhosts/fred-weasley.com/html/index.php
- ls -l /var/www/vhosts/fred-weasley.com/html
rw-r---- 1 fred fred 116 Jul 25 2004 index.php
}}}
Now fire up your web-browser and check the output of your PHP script. (Here: http://www.fred-weasley.com/index.php)
If everything went well, you will see an output showing the User ID of the user ''fred'', and the Group ID of the user group ''fred''. (You can see these IDs in the files ''/etc/passwd'' and ''/etc/group'').
10. Automatically start the FastCGI startup scriptsOptionally, you may also create a crontab entry to automatically execute the FastCGI startup scripts when your server boots.
Use the following command to edit your crontab:
{{{- crontab -e
}}}
Now add the following line:
{{{
@reboot for i in /var/www/fastcgi/startup/*.sh; do $i; done
}}}
And finally type ":x" to save and exit.
This crontab entry will execute all .sh files found in the /var/www/fastcgi/startup directory after the server has booted.
Congratulations! You now have a working fast server configuration with individual (separate) user rights.
LimitationsUsing this model you are creating a separate pool of fastcgi processes for each user. This means that no memory will be shared between these processes. Therefore, if you use this model for a machine with a large number of users you will need a significant amount of available RAM. Also, if you use any
PHP opcode cache such as xcache, apc or eaccelerator, this model means that each user will get their own dedicated cache (which is a good thing from a security perspective, but bad for memory usage). You can tailor the memory used by having different php.ini files that configure the accelerator with differing cache sizes, and by altering the value of PHP_FCGI_CHILDREN in each user's startup.sh script.
In FreeBSD (6.2) every user can be in a maximum of 14 groups. This is the upper bound for webhost-fastcgi-instances, as your lighty-user (www) needs access to those sockets. I installed my webhost 1-2 years ago in this way and run in trouble a few weeks ago while adding www to it's 15th group. No error-msg gaves a hint. Go, and google for it. By the way: Is there a solution? ;)
Updated by Anonymous over 16 years ago · 33 revisions