由一个错误学到的一些php安全配置问题

错误

在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) [<a href='function.require-once'>function.require-once</a>]: 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() [<a href='function.require'>function.require</a>]: 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直接使用文件的全路径,错误信息就更清楚了:

<b>Warning</b>:  require_once() [<a href='function.require-once'>function.require-once</a>]: 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 <b>/var/www/vhosts/fwolf.com/httpdocs/info.php</b> on line <b>4</b><br />
<br />
<b>Warning</b>:  require_once(/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php) [<a href='function.require-once'>function.require-once</a>]: failed to open stream: Operation not permitted in <b>/var/www/vhosts/fwolf.com/httpdocs/info.php</b> on line <b>4</b><br />

<br />
<b>Fatal error</b>:  require_once() [<a href='function.require'>function.require</a>]: Failed opening required '/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php' (include_path='.:/var/www/vhosts/fwolf.com/include') in <b>/var/www/vhosts/fwolf.com/httpdocs/info.php</b> on line <b>4</b><br />

原来是有个open_basedir限制,找了一下是在$HOME/conf/httpd.include里,这个文件是由plesk自动维护的:

<IfModule sapi_apache2.c>
    php_admin_flag engine on
    php_admin_flag safe_mode off
    php_admin_value open_basedir "/var/www/vhosts/fwolf.com/httpdocs:/tmp"
</IfModule>
<IfModule mod_php5.c>
    php_admin_flag engine on
    php_admin_flag safe_mode off
    php_admin_value open_basedir "/var/www/vhosts/fwolf.com/httpdocs:/tmp"
</IfModule>

看到没,只允许包含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的删除没有包含,手工删除文件就可以了)设置好大概就是这个样子:

using php-cgi instead of mod_php, plesk event manager

还要把/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

修改之:

<Directory /var/www/vhosts>
    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
</Directory>

禁用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触发脚本来实现更加简洁。

参考

4 thoughts on “由一个错误学到的一些php安全配置问题”

  1. 你试试这个效率怎么样? 印象中 suphp , suexec 都是很慢的…

    Reply

    Fwolf reply on July 13th, 2008 23:04:26:

    感觉还可以吧,新主机比旧主机在内存使用上节省很多 不太好直接对比。 再说,原来用的suexec比这个更慢,虽然都比不上mod_php

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *