介绍
众所周知,LEMP(Linux,nginx,MySQL,PHP)为运行PHP站点提供了无与伦比的速度和可靠性。 这种流行的的其他好处,如安全和隔离不太受欢迎。
在本文中,我们将向您展示使用不同Linux用户在LEMP上运行站点的安全和隔离优势。 这将通过为每个nginx服务器块(站点或虚拟主机)创建不同的php-fpm池来完成。
先决条件
本指南已在Ubuntu 14.04上测试。 所描述的安装和配置在其他OS或OS版本上类似,但配置文件的命令和位置可能不同。
它还假设您已经设置了nginx和php-fpm。 如果没有,请按照步骤之一,从文章的第三步如何在Ubuntu 14.04安装Linux,nginx的,MySQL和PHP(LEMP)的 。
本教程中的所有命令都应以非root用户身份运行。 如果需要该命令的root访问权限,它会在前面加sudo
。 如果你不已经有设置,请按照本教程: 使用Ubuntu 14.04初始服务器设置 。
您还需要一个指向Droplet中除了默认的测试完全限定域名(FQDN) localhost
。 如果您还没有一个在眼前,你可以使用site1.example.org
。 编辑/etc/hosts
有你喜欢的编辑这样的文件sudo vim /etc/hosts
,加入这一行(替换site1.example.org
与你的FQDN,如果你正在使用它):
...
127.0.0.1 site1.example.org
...
保护LEMP的原因另外
在常见的LEMP设置下,只有一个php-fpm池为同一用户下的所有站点运行所有PHP脚本。 这造成两个主要问题:
- 如果一个nginx服务器上的Web应用程序块(即子域或单独的站点)被攻破,此Droplet上的所有站点也将受到影响。 攻击者能够读取其他站点的配置文件,包括数据库详细信息,甚至可以更改其文件。
- 如果你想让用户访问您的Droplet上的网站,你实际上将授予他访问所有网站。 例如,您的开发人员需要在登台环境中工作。 然而,即使有非常严格的文件权限,你仍然可以访问所有的网站,包括你的主要网站,在同一个Droplet。
上面的问题是通过创建一个不同的池在不同的用户为每个站点php-fpm解决。
第1步 - 配置php-fpm
如果你已经涵盖了先决条件,那么你应该已经在Droplet上有一个功能网站。 除非你指定FQDN为它定制的,你应该能够在FQDN下访问localhost
本地或Droplet远程的IP地址。
现在,我们将创建一个具有自己的php-fpm池和Linux用户的第二个站点(site1.example.org)。
让我们从创建必要的用户开始。 为了最佳隔离,新用户应该有自己的组。 因此,首先创建用户组site1
:
sudo groupadd site1
然后请创建属于此组的用户site1:
sudo useradd -g site1 site1
到目前为止,新用户site1没有密码,不能登录Droplet。 如果你需要有人提供可以直接访问这个网站的文件,那么你应该用命令该用户创建一个密码sudo passwd site1
。 使用新的用户/密码组合,用户可以通过ssh或sftp远程登录。 欲了解更多信息和安全细节检查文章安装有限目录访问辅助SSH / SFTP用户 。
接下来,为site1创建一个新的php-fpm池。 php-fpm池在其本质上只是一个普通的Linux进程,在某些用户/组下运行,并在Linux套接字上监听。 它也可以侦听IP:端口组合,但这将需要更多的Droplet资源,它不是首选的方法。
默认情况下,在Ubuntu 14.04每个PHP-FPM池应在目录中的文件配置/etc/php5/fpm/pool.d
。 与扩展的每个文件.conf
这个目录中在php-fpm的全局配置自动加载。
因此,对于我们的新网站让我们创建一个新的文件/etc/php5/fpm/pool.d/site1.conf
。 你可以用你喜欢的编辑器这样做:
sudo vim /etc/php5/fpm/pool.d/site1.conf
此文件应包含:
[site1]
user = site1
group = site1
listen = /var/run/php5-fpm-site1.sock
listen.owner = www-data
listen.group = www-data
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /
在上面的配置注意这些具体选项:
-
[site1]
是池的名称。 对于每个池,你必须指定一个唯一的名称。 -
user
和group
立为Linux用户和新池将其下运行的组。 -
listen
应指向为每个池一个独特的位置。 -
listen.owner
和listen.group
定义监听器的所有权,即新的PHP-FPM池的插座。 Nginx必须能够读取这个套接字。 这就是为什么插座与下运行nginx的用户和组的创建-www-data
。 -
php_admin_value
允许您设置自定义的PHP配置值。 我们用它来禁用它可以运行Linux命令功能-exec,passthru,shell_exec,system
。 -
php_admin_flag
类似于php_admin_value
,但它仅仅是为布尔值,即打开和关闭的开关。 我们将禁用PHP函数allow_url_fopen
,允许PHP脚本来打开远程文件,并可以通过攻击者可以使用。
注:以上php_admin_value
和php_admin_flag
值可能会在全球范围也可适用。 但是,一个网站可能需要它们,这就是为什么默认情况下它们没有配置。 php-fpm池的优点是它允许您微调每个站点的安全设置。 此外,这些选项可用于安全范围之外的任何其他PHP设置,以进一步定制站点的环境。
该pm
选项是当前安全议题之外,但你应该知道,他们允许您配置池的性能。
该chdir
选项应该是/
这是文件系统的根。 除非你使用另一种重要选择这不应该被改变chroot
。
选项chroot
不包含在故意上述结构。 它将允许您在被监控的环境中运行池,即锁定在目录中。 这是伟大的安全,因为你可以锁定网站的web根内的池。 然而,这个最终的安全性将导致严重的问题,任何体面的PHP应用程序,它依赖于系统二进制文件和应用程序,如Imagemagick,将不可用。 如果你有进一步的兴趣这个主题,请阅读文章如何使用Firejail建立一个WordPress安装在监禁的环境 。
完成上述配置后,重新启动php-fpm以使新设置在命令中生效:
sudo service php5-fpm restart
通过搜索其进程,如下所示验证新池是否正确运行:
ps aux |grep site1
如果你遵循到这里的确切指示,你应该看到类似的输出:
site1 14042 0.0 0.8 133620 4208 ? S 14:45 0:00 php-fpm: pool site1
site1 14043 0.0 1.1 133760 5892 ? S 14:45 0:00 php-fpm: pool site1
红色是进程或php-fpm池运行的用户 - site1。
此外,我们将禁用opcache提供的默认php缓存。 这个特定的缓存扩展可能是伟大的性能,但它不是为了安全,我们将在后面看到。 要禁用它编辑该文件/etc/php5/fpm/conf.d/05-opcache.ini
具有超级用户权限,并添加一行:
opcache.enable=0
然后再次重新启动PHP-FPM( sudo service php5-fpm restart
使设置生效)。
第2步 - 配置nginx
一旦我们为我们的网站配置了php-fpm池,我们将在nginx中配置服务器块。 为此,请创建一个新的文件/etc/nginx/sites-available/site1
与你喜欢的编辑器是这样的:
sudo vim /etc/nginx/sites-available/site1
此文件应包含:
server {
listen 80;
root /usr/share/nginx/sites/site1;
index index.php index.html index.htm;
server_name site1.example.org;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm-site1.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
上面的代码显示了nginx中服务器块的常见配置。 注意有趣的突出部分:
- Web根目录是
/usr/share/nginx/sites/site1
。 - 服务器名称使用FQDN
site1.example.org
这是本文的先决条件中提到的之一。 -
fastcgi_pass
指定的PHP文件的处理程序。 对于每一个网站,你应该使用不同的Unix套接字如/var/run/php5-fpm-site1.sock
。
创建web根目录:
sudo mkdir /usr/share/nginx/sites
sudo mkdir /usr/share/nginx/sites/site1
为了使上述网站必须在目录中创建一个符号链接它/etc/nginx/sites-enabled/
。 这可以使用命令:
sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1
最后,重启nginx使更改生效,如下所示:
sudo service nginx restart
第3步 - 测试
对于运行测试,我们将使用知名的phpinfo函数,它提供有关php环境的详细信息。 创建名下的新文件info.php
仅包含行<?php phpinfo(); ?>
<?php phpinfo(); ?>
。 您将首先在默认nginx的网站及其网页根需要这个文件/usr/share/nginx/html/
。 为此,你可以使用这样的编辑器:
sudo vim /usr/share/nginx/html/info.php
之后,将文件复制到其他网站(site1.example.org)的web根目录,如下所示:
sudo cp /usr/share/nginx/html/info.php /usr/share/nginx/sites/site1/
现在您可以运行最基本的测试来验证服务器用户。 您可以使用浏览器或从Droplet终端和lynx,命令行浏览器执行测试。 如果你没有对你的Droplet猞猁然而,用命令安装sudo apt-get install lynx
。
首先检查info.php
从默认的站点文件。 它应该可以在localhost下这样访问:
lynx --dump http://localhost/info.php |grep 'SERVER\["USER"\]'
在上面的命令,我们只对变量筛选使用grep输出SERVER["USER"]
它代表服务器用户。 对于默认的网站上的输出应显示默认的www-data
类似这样的用户:
_SERVER["USER"] www-data
同样,下一步检查site1.example.org的服务器用户:
lynx --dump http://site1.example.org/info.php |grep 'SERVER\["USER"\]'
您应该看到这一次在输出中site1
用户:
_SERVER["USER"] site1
如果您已经在每个php-fpm池基础上进行了任何自定义php设置,那么您也可以通过过滤您感兴趣的输出以上述方式检查其对应的值。
到目前为止,我们知道我们的两个网站运行在不同的用户,但现在让我们看看如何保护一个连接。 为了演示我们在本文中解决的安全问题,我们将创建一个包含敏感信息的文件。 通常这样的文件包含到数据库的连接字符串,并包括数据库用户的用户和密码细节。 如果任何人发现该信息,该人能够对相关网站做任何事情。
用你喜欢的编辑器创建你的主站一个新的文件/usr/share/nginx/html/config.php
。 该文件应包含:
<?php
$pass = 'secret';
?>
在上述文件中,我们定义了一个变量叫pass
持有的价值secret
。 当然,我们希望限制对此文件的访问,因此我们将其权限设置为400,这样只能访问文件所有者。
要将权限更改为400,请运行命令:
sudo chmod 400 /usr/share/nginx/html/config.php
此外,我们的主网站下的用户运行www-data
应该能够读取该文件谁。 因此,将文件的所有权更改为该用户,如下所示:
sudo chown www-data:www-data /usr/share/nginx/html/config.php
在我们的例子中,我们将使用名为另一个文件/usr/share/nginx/html/readfile.php
读取秘密信息并打印。 此文件应包含以下代码:
<?php
include('/usr/share/nginx/html/config.php');
print($pass);
?>
更改此文件的所有权www-data
,以及:
sudo chown www-data:www-data /usr/share/nginx/html/readfile.php
要确认所有权限和所有权是正确的在Web根目录中运行命令ls -l /usr/share/nginx/html/
。 您应该看到类似的输出:
-r-------- 1 www-data www-data 27 Jun 19 05:35 config.php
-rw-r--r-- 1 www-data www-data 68 Jun 21 16:31 readfile.php
现在访问您的默认网站后文件,命令lynx --dump http://localhost/readfile.php
。 你应该能够看到打印输出secret
这表明敏感信息的文件是相同的网站,这是预期的正确行为中访问。
现在将文件复制/usr/share/nginx/html/readfile.php
你的第二个网站,site1.example.org是这样的:
sudo cp /usr/share/nginx/html/readfile.php /usr/share/nginx/sites/site1/
为了保持站点/用户关系的顺序,请确保在每个站点内文件由相应的站点用户拥有。 通过使用以下命令将新复制的文件的所有权更改为site1:
sudo chown site1:site1 /usr/share/nginx/sites/site1/readfile.php
为了确认您已经设置正确的权限和文件的所有权,请列出site1的Web根目录的内容,用命令ls -l /usr/share/nginx/sites/site1/
。 你应该看到:
-rw-r--r-- 1 site1 site1 80 Jun 21 16:44 readfile.php
然后尝试访问从site1.example.com同一个文件的命令lynx --dump http://site1.example.org/readfile.php
。 您将只能看到返回的空白空间。 此外,如果您搜索Nginx的和grep命令的错误日志错误sudo grep error /var/log/nginx/error.log
您将看到:
2015/06/30 15:15:13 [error] 894#0: *242 FastCGI sent in stderr: "PHP message: PHP Warning: include(/usr/share/nginx/html/config.php): failed to open stream: Permission denied in /usr/share/nginx/sites/site1/readfile.php on line 2
注意:您也将看到一个类似的错误在山猫的输出,如果你有display_errors
设置为On
在php-fpm的配置文件/etc/php5/fpm/php.ini
。
该警告表明,从site1.example.org站点脚本无法读取敏感文件config.php
从主站点。 因此,在不同用户下运行的站点不能损害彼此的安全性。
如果回到本文结尾的配置部分,您将看到我们已禁用opcache提供的默认缓存。 如果你是好奇,为什么,尝试启用再次用超级用户权限设置opcache opcache.enable=1
在文件/etc/php5/fpm/conf.d/05-opcache.ini
并重新启动PHP5-FPM用命令sudo service php5-fpm restart
。
令人惊讶的是,如果您以完全相同的顺序再次运行测试步骤,则无论其所有权和权限如何,您都可以读取敏感文件。 opcache中的这个问题已经报道了很长时间,但到本文的时候,它还没有被修复。
结论
从安全角度来看,对于同一Nginx Web服务器上的每个站点,使用不同用户的php-fpm池非常重要。 即使它带来了小的性能损失,这种隔离的好处可以防止严重的安全漏洞。
本文中描述的想法不是唯一的,它存在于其他类似的PHP隔离技术,如SuPHP。 然而,所有其他替代品的性能比php-fpm差。