使用独立用户权限设置FastCGI和PHP¶
首先请注意:本操作指南仅提供一种建议性的实现方式,如果您选择不同的做法,或因此导致任何问题,请勿责怪他人或本指南……
也可使用Execwrap或php-fpm实现此功能,但本操作指南不涵盖这些方法。
注意:这仅适用于*nix类操作系统。我不知道如何在Windows上实现此功能。
简介¶
为个人用户/客户运行网站托管服务,在设置Web服务器时需要额外费一番心思。
基本上,您为每个用户在Web服务器上提供一个独立的(普通)用户账户。然后,用户将她的PHP脚本文件上传到她自己的虚拟主机文档根目录。
我们想要做的是,以管理相应虚拟主机的用户的相同权限执行所有PHP脚本文件。如果实现这一点,您可以确保您的任何用户都无法浏览其他用户的PHP脚本。
考虑以下在没有对PHP脚本进行独立用户权限设置的Web服务器上执行的PHP脚本(请勿尝试这样做,否则您可能会引来警察!)
#!php <?php $filename = "/path_to_other_users_vhost_root/index.php"; $handle = fopen($filename, "rb"); $contents = fread($handle, filesize($filename)); fclose($handle); echo $contents; ?>
这将读取(并显示)其他用户PHP脚本的源代码。该源代码可能包含访问该用户MySQL数据库的密码,或其它有趣的信息。您甚至可以编写一个PHP脚本,将PHP脚本文件写入其他用户的虚拟主机目录!
这就是我们想要摆脱的设置!
PHP内置的safe_mode如何?¶
我在这里不会说PHP的任何坏话,但强烈不建议使用PHP内置的safe_mode功能。(详见php.net上的safe_mode文档。)
然而,有一些php.ini设置可以在不修改源代码的情况下阻止或减缓最常见的攻击形式。要阻止php的远程访问,请参阅allow_url_fopen;要阻止php包含远程文件,请参阅allow_url_include。设置open_basedir是减缓攻击者的一种好方法,但它不能替代用户权限。为了减缓某些形式的会话劫持,可以关闭session.use_trans_sid。
最保险的做法是依赖操作系统的内置用户权限。
安装¶
我们假设您已经安装了Lighttpd,并且安装了支持FastCGI的PHP。(如何安装支持FastCGI的PHP)
您需要以root用户身份登录才能执行此操作。
1. 将用户添加到操作系统¶
(仅当您尚未添加用户时才需要此步骤。)
您必须为每个希望赋予独立用户权限的用户在操作系统中添加一个用户账户,以拒绝访问其他用户的源代码。
让我们假设我们需要创建三个用户(fred、george和ron)
#!ShellExample # useradd fred # useradd george # useradd ron
2. 将用户组添加到操作系统¶
您需要为上面添加的每个用户添加一个用户组。为了简单起见,我们仅将用户组命名相似。
#!ShellExample # groupadd fred # groupadd george # groupadd ron
现在,您需要将用户添加到这些用户组中。对于每个用户组,必须有两个成员:相应的用户和lighttpd守护进程用户。
通过使用您喜欢的文本编辑器编辑/etc/group文件来配置用户组。
文件内容应大致如下所示(组号可能有所不同)
..... [lots of stuff above] fred:x:441:fred,lighttpd george:x:442:george,lighttpd ron:x:443:ron,lighttpd
您也可以使用类似这样的sed命令
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
这些命令将用户和lighttpd用户添加到组中。
3. 设置文件系统结构¶
假设您希望将所有与Web服务器虚拟主机相关的文件都保存在/var/www目录下。(当然,您可以选择其他位置,只需确保上面创建的用户对该目录具有读写和执行权限即可。(即chmod 755 /var/www && chown root:root /var/www)。
3.1 创建服务器根目录¶
现在,创建两个目录:一个用于存放只有root用户才能访问的启动脚本,另一个用于存放所有虚拟主机。
#!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
3.2 为每个虚拟主机创建一个目录¶
现在,在/var/www/vhosts目录中为每个虚拟主机创建一个目录,并设置适当的用户权限。
#!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
现在我们已经创建了三个目录,这三个用户无法查看彼此的文件;但是,lighttpd守护进程用户可以查看所有内容。
3.3 为每个虚拟主机创建目录结构¶
现在,我们想要为每个虚拟主机创建所需的目录结构
#!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
您需要为每个虚拟主机重复此操作,将用户名'fred'替换为相应的用户名。
3.4 为每个用户创建一个FastCGI目录¶
现在我们要做有趣的事情了!
现在,进入/var/www/fastcgi目录,我们希望在这里为每个用户创建一个目录。(完成之后,这些目录将存放FastCGI服务器进程的套接字)
#!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
(请注意,lighttpd用户可以读取所有目录,而这三个用户只能访问自己的目录。)
4. 为每个用户创建一个FastCGI启动脚本¶
创建一个目录来存放所有FastCGI启动脚本
#!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
现在,进入/var/www/fastcgi/startup目录,使用您喜欢的文本编辑器为fred创建一个启动脚本(我们称之为fred-startup.sh)
#!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
请注意路径、USERID和GROUPID。
请注意,在此示例中,php进程以我们上面创建的用户身份运行
('fred')。这意味着php代码将对html文件和
php文件具有写入权限。这可能很方便,但也可能带来安全风险。
或者,您可以将USERID设置为'nobody'(或任何其他没有任何
特定权限的用户),以拒绝php进程的写入权限。
您需要重复此过程,并在/var/www/fastcgi/startup目录中为每个用户创建一个启动脚本。(只需复制文件并用正确的值替换FCGISOCKET、USERID和GROUPID即可)。
记住为所有启动脚本设置执行权限
#!ShellExample # cd /var/www/fastcgi/startup # chmod 750 *
5. 检查您的PHP配置¶
如果您不确定php.ini的位置,只需运行以下命令
#!ShellExample $ php-cgi -i | grep php.ini
请检查您的php.ini中是否包含以下行
cgi.fix_pathinfo=1
如果您取消了第4点中shell脚本的PHPRC行的注释,请确保php.ini具有正确的拥有者和权限。为了使一切正常工作,它必须是
chmod 644 php.ini chown root:root php.ini
6. 执行所有FastCGI启动脚本¶
现在,启动所有FastCGI服务器进程
#!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
如果出现任何错误消息,请重新检查您的启动脚本以及/var/www/fastcgi目录(包括所有用户子目录)的权限。
7. 在lighttpd服务器中配置虚拟主机¶
使用您喜欢的文本编辑器编辑/etc/lighttpd.conf
.....[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" ) ) ) }
请注意每个虚拟主机的FastCGI套接字路径。
8. 重启lighttpd守护进程¶
只需运行此命令
#!ShellExample # /etc/init.d/lighttpd restart
如果出现任何错误,请重新检查您的/etc/lighttpd.conf配置文件。
9. Hello World!¶
现在,以用户fred的身份登录,并在其虚拟主机中创建一个PHP脚本文件(例如/var/www/vhosts/fred-weasley.com/html/index.php)
#!php <?php echo "<h1>Hello World!</h1>"; echo "<p>Current User ID is: ". posix_getuid(); echo "<p>Current Group ID is: ". posix_getgid(); ?>
另外,请确保设置文件权限
#!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
现在启动您的Web浏览器并检查PHP脚本的输出。(此处示例:http://www.fred-weasley.com/index.php)
如果一切顺利,您将看到显示用户fred的用户ID和用户组fred的组ID的输出。(您可以在/etc/passwd和/etc/group文件中查看这些ID)。
10. 自动启动FastCGI启动脚本¶
可选地,您还可以创建一个crontab条目,以便在服务器启动时自动执行FastCGI启动脚本。
使用以下命令编辑您的crontab
# crontab -e
现在添加以下行
@reboot for i in /var/www/fastcgi/startup/*.sh; do $i; done
最后输入“:x”保存并退出。
此crontab条目将在服务器启动后执行/var/www/fastcgi/startup目录中找到的所有.sh文件。
恭喜!您现在拥有了一个功能正常且具有独立用户权限的快速服务器配置。
限制¶
使用此模型,您将为每个用户创建一个独立的FastCGI进程池。这意味着这些进程之间不会共享内存。因此,如果您在拥有大量用户的机器上使用此模型,将需要大量的可用内存(RAM)。此外,如果您使用任何
PHP操作码缓存(如xcache、apc或eaccelerator),此模型意味着每个用户都将获得自己的专用缓存(这从安全角度来看是好事,但对内存使用不利)。您可以通过使用不同的php.ini文件配置加速器以不同的缓存大小,以及修改每个用户startup.sh脚本中的PHP_FCGI_CHILDREN值来调整内存使用。
在FreeBSD (6.2) 中,每个用户最多可以属于14个组。这是webhost-fastcgi实例的上限,因为您的lighty用户(www)需要访问这些套接字。我一两年前以这种方式安装了我的web主机,几周前在将www添加到其第15个组时遇到了麻烦。没有错误消息给出提示。去谷歌一下吧。顺便问一下:有解决方案吗?😉 - 是的,FreeBSD 8.0将把此限制提高到1024。
权限¶
mod_fastcgi有一个选项:check-local。启用时,Lighty会使用其用户检查文件是否存在于文档根目录中。如果您不希望Lighty的用户访问文档根目录,则必须禁用此选项。