2008年7月18日星期五

由一个错误学到的一些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) [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的删除没有包含,手工删除文件就可以了)设置好大概就是这个样子:

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 

修改之:

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

参考

Tags: , , , , , , , , , ,

Related posts

Source: http://www.fwolf.com/blog/post/411