关于nginx/php-fpm/apache mod_php中的安全配置,可参考我之前的另外一个文章:
《正确设置nginx/php-fpm/apache 提高网站安全性 防止被挂木马》
http://zhangxugg-163-com.iteye.com/blog/1171572
一、基础知识准备
在任何配置开始前,一定要搞清楚这几个概念,如果这几个概念没有彻底弄清楚,就无法理解后续的配置的意义了, 有经验的系统管理员可跳过。
1. 站点文件所有者账户是什么,文件的权限又是什么?
2.nginx, php-fpm, apache用什么账户运行?
3.网站的哪些目录和文件是可写的?这里的可写,是指对于nginx ? php-fpm ? 还是apache ?
4.如何禁止php代码被执行?
Linux的最基本的权限是:文件的所有者,对其所属所有文件都有任何权限(root权限不受限制),读写执行权限分别被标识为4 2 1
我看到最多的是,一些系统管理员为了省事,让php-fpm/apache以网站文件所有者,这样方便php程序在任何位置生成文件, 但这样配置却可能造成严重的安全问题。
上面几个问题的回复:
1.在linux目录中,使用ls -l命令即可查看当前目录所有者,第3、4列分别文件所属于的用户/组,如图示,这是yii框架的protected目录中的文件列表
我们可以看到,文件所有者和组是ftp, runtime目录权限是任何人有读写执行权限.
2.站点中,往往需要可写目录,用于运行时生成文件(缓存、静态文件、附件上传等),所以这个可写,是针对php程序本身而言的,nginx+php-fpm模式下,nginx只是负责把请求转发给php-fpm进程,所以最终的生成文件是由php-fpm产生的。但是Apache mod_php却有所不同,mod_php作为apache的一个模块,其权限继承自apache, 故说成apache的可写目录,也不为错。
可以用命令 ps aux | egrep 'nginx|php-fpm|apache' 查看nginx/php-fpm/apache运行账户
3.与开发人员沟通,了解可写目录及其意义
4.如果禁止php代码被执行?可能由于程序代码漏洞,木马文件被上传到了附件目录,你可能认为在linux中去掉这个目录的执行权限就行了?
chmod a-x -R uploadfile
但这样就导致apache无法读取此目录的文件,导致附件文件无法被访问了。另一方面,这种方式也是错误的。即便是去掉附件文件本身的执行权限(普通文件本身,是没有执行权限),也是行不通的,要先了解php的运行机制。
php的常用运行方式有以下几种:
A . 以nginx+php-fpm的方式运行
B. 以apache mod_php方式运行
C. 命令行下,使用php <filename.php> 方式运行
D. 在php脚本第一行添加:#!/usr/local/php/bin/php, 并给此文件添加执行权限,就可以把它当成一shell脚本运行了。这种文件并不常见。
这几种方式中,只有最后一种方式才需要执行权限,其它几种方式下,只要php解释器进程对php脚本有读取权限,即可运行。关于如何禁止php脚本的执行,可参阅作者的上述文章。
二、safe_mode
php的安全模式备受争议,因为它涉及系统配置,而且它是使用了windows的配置思想,在Linux环境中并不适合。简单地说,开启safe_mode后,php进程本身只允许打开属于它自己的脚本文件。也就是说,在多站点环境中,我们必须为每个站点建立一个用户,并让php进程以对应的账户权限运行, 这样它可以对自己的站点文件有最高权限,但对其它用户的文件,则没有任何权限。听起来不错,但事实上这个配置起来的工作量相当繁琐而且极容易出错:
1. 如果站点很多,需要建立大量账户,需要将站点目录分别授权给这些用户
2.php-fpm需要建立大量的进程池,并指定不同的账户身份, 可能造成fpm进程资源浪费
3.apachce不能配置多个运行账户,只能指定一个。
4.共享目录(如/tmp, /dev/shm等需要单独指定),并不是一件容易的事。
可见,正因为safe_mode配置如此繁琐, php新版将取消safe_mode的支持,PHP手册上已经描述很清楚:
5. 如果站点要迁移到其它机器,用户账户也需要完全迁移,这也是很繁琐的工作量。
安全模式自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。
所以,依赖安全模式的方式,在php新版本中将不会被支持,笔者编写这个文章时,php 7.0.2已经发布。
三、为什么要用open_basedir
open_basedir 用于限制php进程可以打开的目录前缀,一般来说,php程序除了需要读取本站点的文件外,还往往会用到/tmp /dev/shm目录。
假设站点位于 /data/wwwroot/site.cn,在运行时要用到/tmp, /dev/shm, /proc目录,那么可以在php.ini中这样设置
open_basedir = /data/wwwroot/site.cn:/tmp:/dev/shm:/proc
如果php中使用include, require, fopen, gzopen等函数加载其它目录文件,就会报错:
require_once(): open_basedir restriction in effect. File(file.php) is not within the allowed path(s): (PATH)
Warning: require_once(file.php): failed to open stream: Operation not permitted in
Fatal error: require_once(): Failed opening required ...
这个参数在 php 5.2.3时只能在php.ini中配置,无法在运行时配置。php 5.2.3时,可以在任何位置设置。
显然,针对一个有大量站点的主机, 使用多个php.ini的方式,就非常难以配置。方法有两种:
1.在php-fpm.conf配置文件中,为每个进程池指定不同的限定目录
[www]
php_admin_value[open_basedir] = /var/www/www.example.com:/usr/share/php5:/tmp:/usr/share/phpmyadmin:/etc/phpmyadmin
php_admin_value是指此值一旦设置,无法在运行时被修改。但这种方法,却并不被推荐的,因为每个站点都需要建立一个进程池,有100个站点,就需要建立100个进程池,而且这些php进程,无法互相使用,只能是站点独享,造成资源极大浪费。
2.nginx通过fastcgi协议和php-fpm通信时,可以指定一些参数,用于在请求开始前,修改php的配置参数, 这是最为方便有效的方法,强制推荐
fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root/:/tmp/:/proc/:/dev/shm";
这是php-fpm特有的功能,表示从fastcgi客户端可以接收一些参数指定
由于$document_root变量的引入,我们就可以直接批量配置了。
这样一来,所有站点可共享同一个进程池,同时又解决了分别限制访问目录的问题。我们可以把这个指令加入到nginx的fastcgi_params配置文件中,这样所有站点就立即生效,解决了难题
可以用以下php代码测试一下
<?php
echo ini_get('open_basedir');
ini_set('open_basedir', '/etc/');
echo ini_get('open_basedir');
可见已经设置为所期望的限制,并无法在运行时进行修改的。 通过这个举一反三,我们可以在nginx根据需要指定php的各种配置了