众所周知,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
。没有服务器的同学可以在这里购买,不过我个人更推荐您使用免费的腾讯云开发者实验室进行试验,学会安装后再购买服务器。
除了默认值之外,您还需要一个指向CVM进行测试的完全限定域名(fqdn)localhost
。如果您手头没有,可以使用site1.example.org
。使用您喜欢的编辑器编辑/etc/hosts
文件,像这样sudo vim /etc/hosts
,并添加以下行(如果您使用它,请把site1.example.org
替换为您的fqdn):
...
127.0.0.1 site1.example.org
...
在一个常见的LEMP设置下,只有一个php-fpm池,它为同一用户下的所有站点运行所有PHP脚本。这带来两个主要问题:
通过创建一个在不同用户下为每个站点运行的不同池,可以在php-fpm中解决上述问题。
如果您已经涵盖了准备条件,那么您应该已经在CVM上拥有一个功能性网站。除非您为其指定了自定义fqdn,否则您应该能够在本地fqdn localhost
或远程的CVM IP 下访问它。
现在我们将使用自己的php-fpm池和Linux用户创建第二个站点(site1.example.org)。
让我们从创建必要的用户开始。为了获得最佳隔离,新用户应该拥有自己的组。首先创建用户组site1
:
sudo groupadd site1
然后,请创建属于该组的用户site1:
sudo useradd -g site1 site1
到目前为止,新用户site1没有密码,无法登录CVM。如果您需要为某人提供对此站点文件的直接访问权限,则应使用该sudo passwd site1
命令为该用户创建密码。使用新的用户/密码组合,用户可以通过ssh或sftp远程登录。
接下来,为site1创建一个新的php-fpm池。php-fpm池本质上只是一个普通的Linux进程,它在某个用户/组下运行并侦听Linux套接字。它也可以监听IP:端口组合,但这需要更多的CVM资源,并且它不是首选方法。
默认情况下,在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配置值。我们用它来禁用可以运行Linuxexec,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根目录中。但是,这种最终的安全性将导致任何依赖于系统二进制文件和Imagemagick等应用程序的不错的PHP应用程序出现严重问题。
完成上述配置后,重新启动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
)以使设置生效。
一旦我们为我们的站点配置了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中服务器块的通用配置。请注意有趣的突出部分:
/usr/share/nginx/sites/site1
。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
为了运行测试,我们将使用众所周知的phpinfo函数,该函数提供有关php环境的详细信息。在名称中创建一个仅包含该<?php phpinfo(); ?>
行的新文件info.php
。您将首先在默认的nginx站点及其Web根目录中使用此/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/
现在,您已准备好运行最基本的测试来验证服务器用户。您可以使用浏览器或CVM终端和lynx(命令行浏览器)执行测试。如果你的CVM上还没有lynx,请使用该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
要确认您已设置文件的正确权限和所有权,请使用该ls -l /usr/share/nginx/sites/site1/
命令列出site1 Web根目录的内容。你应该看到:
-rw-r--r-- 1 site1 site1 80 Jun 21 16:44 readfile.php
然后尝试使用该lynx --dump http://site1.example.org/readfile.php
命令从site1.example.com访问同一文件。您只会看到返回的空白区域。此外,如果使用grepsudo grep error /var/log/nginx/error.log
命令在nginx的错误日志中搜索错误,您将看到:
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
注意:如果已在php-fpm配置/etc/php5/fpm/php.ini
文件中设置On
,您还会在lynx输出中看到类似display_errors
的错误。
警告显示site1.example.org站点中的脚本无法从主站点读取敏感文件config.php
。因此,在不同用户下运行的站点不能损害彼此的安全性。
如果您回到本文配置部分的末尾,您将看到我们已禁用opcache提供的默认缓存。如果您对此感到好奇,请尝试通过在/etc/php5/fpm/conf.d/05-opcache.ini
文件中设置超级用户权限opcache.enable=1
再次启用opcache,然后使用该sudo service php5-fpm restart
命令重新启动php5-fpm 。
令人惊讶的是,如果以完全相同的顺序再次运行测试步骤,您将能够读取敏感文件,无论其所有权和权限如何。opcache中的这个问题已经报告了很长时间,但到编辑本文时尚未修复。
从安全的角度来看,对于同一个Nginx Web服务器上的每个站点,使用具有不同用户的php-fpm池至关重要。即使它带来了很小的性能损失,这种隔离的好处也可以防止严重的安全漏洞。
本文中的想法并不是唯一的,它存在于其他类似的PHP隔离技术中,例如SuPHP。但是,所有其他替代方案的性能都比php-fpm差。
想要了解更多关于Linux的开源信息教程,请前往腾讯云+社区学习更多知识。
参考文献:《How To Host Multiple Websites Securely With Nginx And Php-fpm On Ubuntu 14.04》
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。