错误
在MediaTemple主机从(dv)3.0升级到3.5之后,我遇到的第一个问题就是一个莫名奇妙的php错误:
[Sat Jul 12 04:51:27 2008] [error] [client 121.42.26.81] PHP Warning: require_once(/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php) [function.require-once]: failed to open stream: Operation not permitted in /var/www/vhosts/fwolf.com/httpdocs/info.php on line 4 [Sat Jul 12 04:51:27 2008] [error] [client 121.42.26.81] PHP Fatal error: require_once() [function.require]: Failed opening required '/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php' (include_path='.:/var/www/vhosts/fwolf.com/include') in /var/www/vhosts/fwolf.com/httpdocs/info.php on line 4
因为是migration过来的,所以require的这个文件肯定存在,并且apache用户也的确有权访问,那问题出在哪里呢?
如果换一种方式,require直接使用文件的全路径,错误信息就更清楚了:
Warning: require_once() [function.require-once]: open_basedir restriction in effect. File(/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php) is not within the allowed path(s): (/var/www/vhosts/fwolf.com/httpdocs:/tmp) in /var/www/vhosts/fwolf.com/httpdocs/info.php on line 4
Warning: require_once(/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php) [function.require-once]: failed to open stream: Operation not permitted in /var/www/vhosts/fwolf.com/httpdocs/info.php on line 4
Fatal error: require_once() [function.require]: Failed opening required '/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php' (include_path='.:/var/www/vhosts/fwolf.com/include') in /var/www/vhosts/fwolf.com/httpdocs/info.php on line 4
原来是有个open_basedir
限制,找了一下是在$HOME/conf/httpd.include
里,这个文件是由plesk自动维护的:
php_admin_flag engine on php_admin_flag safe_mode off php_admin_value open_basedir "/var/www/vhosts/fwolf.com/httpdocs:/tmp" php_admin_flag engine on php_admin_flag safe_mode off php_admin_value open_basedir "/var/www/vhosts/fwolf.com/httpdocs:/tmp"
看到没,只允许包含httpdocs下的文件。open_basedir影响的范围是fopen, require, include之类的函数,在一定程度上加强了安全防护。
问题
但open_basedir也有局限性,它不会影响那些执行系统命令的函数,比如exec, system,如果我想偷主机上另外一位同学的文件(内容),也不见得非要去用require包含过来或者种个hack过去,直接system('cat /path/to/file')
不是更省事么?
system函数有时候还是能派上正当用场的,直接禁用不是什么好办法,现在流行chroot,就是用户的/
就是自己的$HOME
,压根儿就访问不到别人的文件,什么open_basedir, exec, dl都不用禁用,我觉得这才是安全和方便的最佳接合点。
以前用(dv)3.0的时候,手工配置使用fastcgi的php5就是这样,每个用户的cgi用自己的身份,在自己的chroot环境下运行。
不过plesk现在的版本8、将来的版本9都没有要直接使用fastcgi解析php的打算,在"更远的计划里",才可怜兮兮的有这么一句:
Use PHP via FastCGI rather than Apache module
参见:Parallels Summit 2008 - Day 1,所以就只能自己动手了。
fastcgi
很走运,找到了一个2天前刚刚出炉的脚本:Script for using php-cgi instead of mod_php,专门针对plesk,禁用掉mod_php,然后用它来配置fcgi解析。
使用环境:Plesk 8.X on Centos 5.X,依赖:
- 禁用mod_php,开启mod_fcgid
- python-curl, PyXML
- php开启–enable-fastcgi, –enable-force-cgi-redirect
文件需要解压到/root/bin/
下,自己一个子目录,幸好我也是用这个bin目录的。
然后在Server -> Control Panel -> Event Manager里添加自定义事件,在增加、修改、删除domain的时候,自动调用这个脚本。(subdomain的删除没有包含,手工删除文件就可以了)设置好大概就是这个样子:
还要把/etc/httpd/conf.d/php.conf
删得只剩一行:
LoadModule php5_module modules/libphp5.so
并且在/etc/httpd/conf.d/fcgid.conf
里加一句:
PHP_Fix_Pathinfo_Enable 1
不过,这种方法作到一半我就没有继续了,因为我想起来前几天一位朋友和我提到过的suPHP。
suPHP
个人感觉suPHP是最"正统"的解决方案,它是以文件属主用户的身份来运行,正好使用各个用户的权限实现访问限制。
没找到centos的mod_suphp包,只好下载suphp 0.6.3源码自己编译,不过之前要先修改src/apache2/mod_suphp.c
,在324行替换掉两行内容:
//AP_INIT_ITERATE("suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"), AP_INIT_ITERATE("suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"), //AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"), AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"),
然后就是编译安装那三板斧:
# ./configure\ --with-apxs=/usr/sbin/apxs\ --with-php=/usr/bin/php-cgi\ --with-logfile=/var/log/suphp.log\ --with-min-uid=10000\ --with-min-gid=10000\ --with-apache-user=apache\ --with-apr=/usr/bin/apr-1-config\ --with-setid-mode=owner\ --prefix=/usr\ --sysconfdir=/etc # make # make install
在/etc/httpd/conf/httpd.conf
中加入一句(这一句也可以放到后面的suphp.conf
中):
LoadModule suphp_module modules/mod_suphp.so
关闭safe_mode,并且注释掉下面两句:
safe_mode = Off #AddType application/x-httpd-php .php #AddType application/x-httpd-php-source .phps
创建suphp的conf文件,使用源码中的conf文件模板:
# cp doc/suphp.conf-example /etc/httpd/conf.d/suphp.conf
修改之:
RemoveHandler x-httpd-php # php_admin_value engine off AddHandler x-httpd-php .php .php3 .php4 .php5 suPHP_AddHandler x-httpd-php suPHP_Engine On suPHP_ConfigPath /etc/php.ini
禁用mod_php,把php.conf
文件换一个扩展名就行了:
# cd /etc/httpd/conf.d # mv php.conf php.conf.bak
创建suPHP的配置文件/etc/suphp.conf
,这个文件和用于apache配置的conf是不一样的,其内容如下,可根据具体环境设定参数:
[global] ;Path to logfile logfile=/var/log/suphp.log ;Loglevel ;loglevel=info ;info, warn, error loglevel=warn ;User Apache is running as ;webserver_user=wwwrun webserver_user=apache ;Path all scripts have to be in docroot=/var/www/vhosts/ ;Path to chroot() to before executing script ;chroot=/mychroot ; Security options ;allow_file_group_writeable=false allow_file_group_writeable=true ;allow_file_others_writeable=false allow_file_others_writeable=true ;allow_directory_group_writeable=false allow_directory_group_writeable=true ;allow_directory_others_writeable=false allow_directory_others_writeable=true ;Check wheter script is within DOCUMENT_ROOT check_vhost_docroot=true ;Send minor error messages to browser ;errors_to_browser=false errors_to_browser=true ;PATH environment variable env_path=/bin:/usr/bin ;Umask to set, specify in octal notation ;umask=0077 umask=0022 ; Minimum UID ;min_uid=100 min_uid=10000 ; Minimum GID ;min_gid=100 ; Consider of psacln, psaserv min_gid=200 ; Use correct permissions for mod_userdir sites ;handle_userdir=true [handlers] ;Handler for php-scripts ;x-httpd-php=php:/usr/bin/php x-httpd-php=php:/usr/bin/php-cgi ;Handler for CGI-scripts x-suphp-cgi=execute:!self
现在,重启apache,就可以啦!如果发现返回空页面,并且错误log中有如下内容:
Premature end of script headers:
那有可能是因为你把cli模式的php可执行文件拿过来当cgi模式的用了,注意他们的区别:
# php -v PHP 5.2.6 (cli) (built: May 2 2008 16:06:40) # php-cgi -v PHP 5.2.6 (cgi-fcgi) (built: May 2 2008 16:01:17)
把正确的cgi模式php执行文件设定到/etc/suphp.conf
中即可。
chroot的疑惑
由于以前为了安全,ssh权限都是限定在chroot环境下,这样用户无法访问自己$HOME之外的内容。使用了suPHP之后,虽然php文件是以用户身份运行的,但却不是chroot的环境。也就是说,"理论上"在php文件执行的时候,可以访问其他用户的文件,这不也是个安全隐患么?
为了这个问题,我翻阅了好多资料,却发现很少人提起这个东西,suPHP安装不复杂,介绍的也不少,就是没有和chroot搭配的,倒是有人提出和fastcgi搭配使用。后来和michael沟通后才突然醒悟,suPHP的伪装身份和chroot是两种机制,之间没有什么联系,所以也就不存在什么配套使用的问题。至于不想让用户访问别人的文件,完全可以通过设定文件权限来实现嘛,不过还是要在安全方面比以前更加留心:
- $HOME下系统自动创建的目录,一般属主都是user:psaserv或者root:root,有些对所有人都有rx权限(755),有些则是750权限,私密文件不要往755权限的目录下放。这些目录一般不宜改为750权限,因为有些文件是其他系统服务需要读取的。
- $HOME下httpdocs, private等目录默认就是禁止所有人访问的,保持这样不要更改,并且httpdocs下的文件你就是搞成777权限,别人也访问不到。
- 用户自建的文件、目录一般为user:psacln权限,主机上所有host用户所属组都是psacln,所以如果不想让别人访问,又没有上级目录的权限限制的话,一定调整为700权限。
- 为了使用更方便,可以把$HOME目录的属主设为用户本身,比如
chown fwolf:psaserv /var/www/vhosts/fwolf.com
,不过这就需要一个个的单独开通了。 - 如果发现其他系统文件中有泄密的,或者其他用户没有设置好权限,存在安全隐患,请及时告诉我或者相应用户,这样我们才是和谐的一家人嘛
取消chroot,还有一个好处就是用户几乎能够使用主机上的所有命令了,不像以前那样用哪个就需要把哪个设置到chroot的jail中,方便多了。
chroot的取消不是自动的,我已经给所有用户加上了可指定/bin/bash
作为登录shell的权限,用户在plesk的站点设置中,把ssh用户的登录更换为/bin/bash
即可,当然如果对安全没有信心,觉得chroot也够用的用户可以保留。
其他
suphp比suexec(就是原来dv3.0升php5的方法)要快一点;比suphp更快的还有suphp_mod_php;再快一些的是mpm-peruser,不过安装配置的麻烦程度也随之递增。
相比而言,suPHP速度还算可以接受(对于负载不是很大的站),配置方便,不用修改每个virtualhost的参数(就是$HOME/conf/vhost.conf),直接改apache的总conf就ok了,当然也比上面fastcgi方式下用event触发脚本来实现更加简洁。
参考
- (dv) HOWTO: Enable PEAR/Set open_basedir.
- CakePHP on Media Temple (dv) 3.5
- suPHP on Plesk with SuSE Linux Enterprise Server 10
- HOW-TO Setup a PLESK Dedicated Server
- Chrooting Apache2 With mod_chroot On Debian Etch
- [suPHP] How to test suPHP
- [實作] suPHP-0.6.2 on Centos 4/5
- Using suphp To Secure A Shared Server,这个网站上这个分类下的一系列文章都不错。
- PHPでもユーザ権限で動作させる-suPHP
Related posts
- 升级MT dv 3.0主机到php5 (9)
- 生成用于web服务器的openssl证书 (0)
- [MediaTemple]从(dv)3.0升级到3.5 (0)
- [MediaTemple]虚拟主机内存优化的一点心得 (2)
- MT主机控制面板Plesk合租用户使用指南 (9)