接着上回的话题,关于一个系统管理员能够给一台跑着多个应用的服务器做些什么来提高整体安全性。除了限制一些危险函数以外,很重要的便是尽可能地隔离不同的应用,以免一个瘫痪/被攻影响所有。其实,限制危险函数也是为了更好地实现应用隔离——接下来我们会看到,对于某些应用隔离方法,限制某些函数的调用是必须的。这里,我会大致按照隔离的效果降序给出几种常用方法。需要注意到的是,一般来说隔离效果越好,对性能的损失和其他副作用也越大。

一、虚拟化

虚拟化几乎可以算是在一台物理机上能做到的最彻底的应用隔离手段了。虚拟化的形式多种多样,从全虚拟、半虚拟,到一些轻量化的使用 cgroup 等实现的“容器”,不一而足,不是这篇文章能够囊括的。就算是最轻量级的虚拟化,在数据安全意味上的隔离性也是非常强的,只要实现正确,几乎不可能相互影响数据,唯一有可能的就是在性能上造成些许干扰。同时,即使是最轻量的虚拟化配置起来也有些麻烦,绝大多数实现都需要安装多套运行时,在存储上造成不少 overhead 不说,配置、维护起来也可能增添一些麻烦,而最大的问题可能就是性能损失了——如果你只有一台入门级服务器,在上面做虚拟化绝对不是一个好选择。

二、chroot 后的多 Apache 实例

这个似乎不是一个很常见的方法,但在理论上是可行的。首先, Apache 经过大量的调校之后是可以 chroot 运行的。这样一来,我们给每个应用配备一套独立的根目录,运行多个 chroot 后的 Apache 实例(整个实例,不单单是 worker ),注意各个实例监听端口不能重复,再在前面放一个统一的 nginx 转发。就可以实现非常理想的数据隔离。通过适当设置各个示例的 worker 数量,还能最大程度保证低效应用不会卡住其他应用。

三、以 CGI 模式运行 PHP (包括 suphp )

这是一个十分巧妙地可以低 overhead 运行多个不同身份的 PHP 。有两种实现方法,一种就是直接使用 fastcgi 或者 php-fpm ,给每个应用分别运行一套,并且正确地指定 run user/group ;另一种则是使用 Apache 插件—— suPHP ,它本身虽然是一个插件,但事实上是做了一层中转,在接到 PHP request 后根据配置文件以适当的用户启动(事实上是通过 setuid 来实现的) php-cgi 来处理请求。

我在一台测试服务器上使用的就是 suphp ,然而这个项目已经很久没有维护了,使用普通的 php-cgi 性能也很差。如果需要在生成环境使用的话,推荐还是通过自己写脚本调用 php-fpm (较新的 PHP 都自带)来实现。但不管用何种方式, CGI 模式的 PHP 都不如 Apache mod 模式快(网络上有很多谣传,声称 php-fpm 甚至比 mod_php 还快),并且会占用大量内存。另外 CGI 模式还会带来一些微妙的配置问题,一些细微处的表现也会不同,需要稍微留意。

四、使用 open_basedir 设置

这个是隔离效果最差,但也是对性能几乎没有影响的方法了。通过在每个 Directory 中设置相应的 open_basedir ,就可以限制此目录下的脚本仅能访问相应的目录。记得要加上 /tmp ,不过不需要显式地加上 session 存储目录(一般是在 /var/lib/php… ),后者 PHP 会自动处理好的。最后,一定要记得使用上一篇文章中提到的函数限制,否则很有可能被绕过。

最后

不管用何种方式,正确地配置文件和目录的权限都是非常重要的!甚至可以说是一切的基础!不过服务器(尤其是多应用服务器)上的权限管理又是另一个值得单独一说的话题了。