如何在Ubuntu 14.04上使用Nginx和Php-fpm安全地托管多个网站

介绍

众所周知,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的原因

在一个常见的LEMP设置下,只有一个php-fpm池,它为同一用户下的所有站点运行所有PHP脚本。这带来两个主要问题:

  • 如果一个nginx服务器块上的Web应用程序(即子域或单独的站点)受到攻击,此CVM上的所有站点也将受到影响。攻击者能够读取其他站点的配置文件,包括数据库详细信息,甚至可以更改其文件。
  • 如果您想让用户访问CVM上的站点,您几乎可以访问所有站点。例如,您的开发人员需要处理登台环境。但是,即使拥有非常严格的文件权限,您仍然可以在同一CVM上访问所有站点,包括您的主站点。

通过创建一个在不同用户下为每个站点运行的不同池,可以在php-fpm中解决上述问题。

第一步 - 配置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]是池的名称。对于每个池,您必须指定唯一的名称。
  • usergroup代表Linux用户和将在其下运行新池的组。
  • listen 应指向每个池的唯一位置。
  • listen.ownerlisten.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_valuephp_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)以使设置生效。

第二步 - 配置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

第三步 - 测试

为了运行测试,我们将使用众所周知的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》

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏沈唁志

怎么使用 Git 和 Github 向开源项目提交 Pull Request

渣渣橘又跟我提了一下毒鸡汤,看了一下之前学的 python 脚本还能用,然后在 Github 上看到了一个爬虫集合的项目,收集各种爬虫

15930
来自专栏Youngxj

144G网页在线制作源码 v1.4

40670
来自专栏Ryan Miao

redis学习之三配置文件redis.conf 的含义

摘自http://www.runoob.com/redis/redis-conf.html 安装redis之后的第一件事,我就开始配置密码,结果总是不生效,而我...

27650
来自专栏木头编程 - moTzxx

CentOS下(rsync+inotify)监听式实现服务器间的备份实时同步

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/de...

41810
来自专栏云计算教程系列

如何在Ubuntu 16.04上使用Concourse CI设置持续集成管道

Concourse CI是一个现代的,可扩展的集成系统,旨在通过可组合的声明性语法自动测试管道。

19020
来自专栏大数据和云计算技术

MongoDB系列11:Munin监控MongoDB

Munin是一个网络资源监控工具,可以帮助分析资源趋势。默认提供了大量的分析图形。以下讲述如何设置MongoDB的Munin监控插件。

20430
来自专栏测试驿栈

JProfiler简明使用教程

 JProfile是一款性能瓶颈分析工具,监控粒度可以细化到某一个类包,堪称神器!我安装了一下9.11的版本,并简单说说使用方法。

1.9K30
来自专栏赵俊的Java专栏

Hexo 扩展 --- 优雅的备份 Hexo 博客

64230
来自专栏不想当开发的产品不是好测试

maven可选依赖(Optional Dependencies)和依赖排除(Dependency Exclusions)

我们知道,maven的依赖关系是有传递性的。如:A-->B,B-->C。但有时候,项目A可能不是必需依赖C,因此需要在项目A中排除对A的依赖。在maven的依赖...

38560
来自专栏后端技术探索

Nginx服务器架构简析

模块化结构的思想是一个很久的概念,但也正是成熟的思想造就了Nginx的巨大优越性。

11930

扫码关注云+社区

领取腾讯云代金券