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

介绍

众所周知,LEMP堆栈(Linux,nginx,MySQL,PHP)为运行PHP网站提供了无与伦比的速度和可靠性。

在本文中,我们将向您展示在LEMP上运行不同Linux用户的网站的安全性和隔离优势,这将通过为每个nginx服务器块(网站或虚拟主机)创建不同的php-fpm池来完成。

前提条件

此指南已在 Ubuntu 14.04 上测试,描述的安装和配置在其他 OS 或 OS 版本上类似,但配置文件的命令和位置可能有所不同。

它还假定你已经安装了 nginx 和 php-fpm. 如果没有,请遵循文章中的第一步和第三步 How To Install Linux, nginx, MySQL, PHP (LEMP) stack on Ubuntu 14.04)。

本教程中的所有命令都应该作为非 root 用户运行. 如果命令需要 root 访问,则将先由 sudo

你还需要一个完全合格的域名(fqdn),它指向Droplet进行测试,除了默认的localhost。如果你没有一个,你可以使用site1.example.org。用你最喜欢的编辑器编辑/etc/hosts文件,如sudo vim /etc/hosts,并添加这个行(如果你正在使用它,则用你的fqdn代替site1.example.org):

1[label /etc/hosts]
2...
3127.0.0.1 site1.example.org
4...

额外保护LEMP的理由

在一个常见的LEMP设置中,只有一个php-fpm池,在同一用户下运行所有PHP脚本。

  • 如果一个 nginx 服务器块上的 Web 应用程序,即子域或单独的网站被破坏,那么这个 Droplet 上的所有网站也将受到影响。攻击者可以读取其他网站的配置文件,包括数据库细节,甚至改变他们的文件。
  • 如果你想让用户访问你的 Droplet 上的一些网站,你实际上会让他访问所有网站。例如,你的开发者需要在舞台环境中工作。然而,即使有非常严格的文件权限,你仍然会让他访问所有网站,包括你的主要网站,在同一个 Droplet.

上述问题在php-fpm中通过创建一个不同的池子来解决,该池子在每个网站的不同用户下运行。

步骤 1 – 配置 php-fpm

如果你已经满足了前提条件,那么你应该已经在Droplet上有一个功能性的网站,除非你为它指定了自定义fqdn,否则你应该能够在fqdn下本地访问它,或者通过Droplet的IP远程访问它。

现在我们将创建一个第二个网站(site1.example.org),拥有自己的php-fpm池和Linux用户。

让我们从创建必要的用户开始。为了获得最佳的隔离,新用户应该有自己的组。

1sudo groupadd site1

然后请创建一个用户网站1属于这个组:

1sudo useradd -g site1 site1

到目前为止,新用户网站1没有密码,无法登录Droplet. 如果您需要为某人提供直接访问本网站的文件,那么您应该为该用户创建一个密码,用命令 sudo passwd site1. 使用新用户/密码组合,用户可以通过ssh或sftp远程登录。

接下来,为网站创建一个新的php-fpm池1. 一个php-fpm池在其本质上只是一个普通的Linux流程,它在某些用户/组下运行,并在Linux插槽上收听。

默认情况下,在 Ubuntu 14.04 中,每个 php-fpm 池都应该被配置为在目录内的一份文件 /etc/php5/fpm/pool.d. 该目录中的每个扩展 .conf 文件都会自动加载到 php-fpm 全球配置中。

因此,对于我们的新网站,让我们创建一个新的文件 /etc/php5/fpm/pool.d/site1.conf. 您可以用您最喜欢的编辑器这样做:

1sudo vim /etc/php5/fpm/pool.d/site1.conf

该文件应包含:

 1[label /etc/php5/fpm/pool.d/site1.conf]
 2[site1]
 3user = site1
 4group = site1
 5listen = /var/run/php5-fpm-site1.sock
 6listen.owner = www-data
 7listen.group = www-data
 8php_admin_value[disable_functions] = exec,passthru,shell_exec,system
 9php_admin_flag[allow_url_fopen] = off
10pm = dynamic
11pm.max_children = 5
12pm.start_servers = 2
13pm.min_spare_servers = 1
14pm.max_spare_servers = 3
15chdir = /

在上面的配置中,请注意这些具体选项:

对于每个池,你必须指定一个独特的名称。 *用户代表Linux用户和新池将运行的组。 *倾听应该指向每个池的独特位置。 *listen.ownerlisten.group定义了倾听者的所有权,即新 php-fpm池的插槽。 Nginx必须能够读取这个插槽。这就是为什么插槽与用户和组一起创建,其中nginx运行 - www-data. *php_admin_value允许你设置自定义的php配置值。我们使用它来取消功能,它可以运行Linux命令 - `exec,passe``,_exhole_ex

<$>[note] ** 注意:** 上面的 php_admin_valuephp_admin_flag 值也可能在全球范围内应用,但是,网站可能需要它们,这就是为什么默认情况下它们没有配置的原因。php-fpm 池的美好之处在于它允许您精确调整每个网站的安全设置。此外,这些选项可以用于任何其他 PHP 设置,不包括安全范围,以进一步定制网站的环境。

pm选项不在当前的安全主题之外,但您应该知道它们允许您配置池的性能。

chdir选项应该是/,这是文件系统的根源,除非你使用另一个重要的选项chroot

chroot选项不包含在上述配置中,它允许您在监狱环境中运行池,即锁定在目录中。这对安全而言非常好,因为您可以锁定池在网站的 Web 根中。然而,这种最终的安全性会给任何依赖系统二进制和 Imagemagick 等应用程序的适当 PHP 应用带来严重问题。如果您对这个主题更感兴趣,请阅读文章 如何在监狱环境中使用 Firejail 来设置 WordPress 安装

一旦您完成了上述配置,请重新启动php-fpm,以便新的设置与命令生效:

1sudo service php5-fpm restart

通过搜索其过程来验证新池是否正常运行:

1ps aux |grep site1

如果您遵循到目前为止的准确指示,您应该看到类似的输出:

1site1 14042 0.0 0.8 133620 4208 ?        S 14:45 0:00 php-fpm: pool site1
2site1 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 并添加行:

1[label /etc/php5/fpm/conf.d/05-opcache.ini]
2opcache.enable=0

然后重新启动 php-fpm(‘sudo service php5-fpm restart’)以使设置生效。

步骤 2 – 配置 nginx

一旦我们为我们的网站配置了php-fpm池,我们就会在nginx中配置服务器封锁. 为此,请使用您最喜欢的编辑器创建一个新的文件 /etc/nginx/sites-available/site1:

1sudo vim /etc/nginx/sites-available/site1

该文件应包含:

 1[label /etc/nginx/sites-available/site1]
 2server {
 3    listen 80;
 4
 5    root /usr/share/nginx/sites/site1;
 6    index index.php index.html index.htm;
 7
 8    server_name site1.example.org;
 9
10    location / {
11        try_files $uri $uri/ =404;
12    }
13
14    location ~ \.php$ {
15        try_files $uri =404;
16        fastcgi_split_path_info ^(.+\.php)(/.+)$;
17        fastcgi_pass unix:/var/run/php5-fpm-site1.sock;
18        fastcgi_index index.php;
19        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
20        include fastcgi_params;
21    }
22}

上面的代码显示了 nginx 中的服务器块的常见配置,请注意有趣的突出部分:

  • Web 根是 /usr/share/nginx/sites/site1.
  • 服务器名称使用 fqdn site1.example.org,这是本文的前提中提到的一个。

创建 Web 根目录:

1sudo mkdir /usr/share/nginx/sites
2sudo mkdir /usr/share/nginx/sites/site1

要启用上述网站,您必须在目录 /etc/nginx/sites-enabled/ 中创建一个对其的simlink。

1sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1

最后,重新启动 nginx 以便更改生效如下:

1sudo service nginx restart

步骤三:测试

为了运行测试,我们将使用已知的 phpinfo 函数,该函数提供有关 php 环境的详细信息. 创建一个名为 info.php 的新文件,该文件只包含行 <?php phpinfo();?>. 您需要这个文件首先在默认的 nginx 网站和它的 Web 根 /usr/share/nginx/html/. 为此,您可以使用这样的编辑器:

1sudo vim /usr/share/nginx/html/info.php

然后将文件复制到另一个网站(site1.example.org)的 Web 根,如下:

1sudo cp /usr/share/nginx/html/info.php /usr/share/nginx/sites/site1/

现在你已经准备好运行最基本的测试来验证服务器用户. 你可以使用浏览器或从Droplet终端和Lynx,命令行浏览器进行测试. 如果你还没有Lynx在Droplet上,请用命令sudo apt-get install lynx安装它。

首先,从您的默认网站检查info.php文件,它应该在 localhost 下可访问:

1lynx --dump http://localhost/info.php |grep 'SERVER\["USER"\]'

在上面的命令中,我们仅将输出过滤为SERVER变量,这代表服务器用户,对于默认网站,输出应该显示默认的www-data用户:

1_SERVER["USER"]                 www-data

同样,接下来检查服务器用户的 site1.example.org:

1lynx --dump http://site1.example.org/info.php |grep 'SERVER\["USER"\]'

你应该在输出中看到这个时候的site1用户:

1_SERVER["USER"]                 site1

如果您已根据每个 php-fpm 池进行任何自定义的 php 设置,那么您也可以通过过滤您感兴趣的输出来检查其相应的值。

到目前为止,我们知道我们的两个网站在不同的用户下运行,但现在让我们看看如何确保连接。 为了展示我们在本文中解决的安全问题,我们将创建一个包含敏感信息的文件。 通常,此类文件包含连接链接到数据库,并包括数据库用户的用户和密码细节。

使用您最喜欢的编辑器创建一个新的文件在您的主要网站 /usr/share/nginx/html/config.php. 该文件应该包含:

1[label /usr/share/nginx/html/config.php]
2<?php
3$pass = 'secret';
4?>

在上面的文件中,我们定义了一个名为pass的变量,该变量具有秘密的值。当然,我们希望限制访问该文件,所以我们将其权限设置为400,这只会让文件的所有者访问读取。

若要將權限變更為 400,請執行以下命令:

1sudo chmod 400 /usr/share/nginx/html/config.php

此外,我们的主要网站运行在用户www-data下,该用户应该能够读取此文件。

1sudo chown www-data:www-data /usr/share/nginx/html/config.php

在我们的示例中,我们将使用另一个名为 /usr/share/nginx/html/readfile.php 的文件来读取秘密信息并打印它。

1[label /usr/share/nginx/html/readfile.php]
2<?php
3include('/usr/share/nginx/html/config.php');
4print($pass);
5?>

更改此文件的所有权为www-data:

1sudo chown www-data:www-data /usr/share/nginx/html/readfile.php

要确认所有权限和所有权是正确的,在 Web 根中运行命令 ls -l /usr/share/nginx/html/

1-r-------- 1 www-data www-data 27 Jun 19 05:35 config.php
2-rw-r--r-- 1 www-data www-data 68 Jun 21 16:31 readfile.php

现在,您可以访问默认网站上的后者文件,使用命令lynx --dump http://localhost/readfile.php 您应该能够看到在输出中打印的秘密,这表明含有敏感信息的文件在同一个网站内可访问,这是预期正确的行为。

现在将文件 /usr/share/nginx/html/readfile.php 复制到您的第二个网站, site1.example.org 如下:

1sudo cp /usr/share/nginx/html/readfile.php /usr/share/nginx/sites/site1/

要保持网站/用户关系的顺序,请确保每个网站中的文件由相应的网站用户拥有。

1sudo chown site1:site1 /usr/share/nginx/sites/site1/readfile.php

要确认您已设置了文件的正确权限和所有权,请用命令 ls -l /usr/share/nginx/sites/site1/列出 site1 web root 的内容。

1-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 搜索错误,您将看到:

12015/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 中设置为 display_errors,您也会在 lynx 输出中看到类似的错误。

警告显示,来自 site1.example.org 网站的脚本无法从主网站读取敏感文件 `config.php。

如果你回到本文的最后的配置部分,你会看到我们已经禁用了由opcache提供的默认缓存. 如果你想知道为什么,请尝试通过在文件中设置超级用户权限 opcache.enable=1以重新启用opcache,并使用命令 sudo service php5-fpm restart重新启动php5-fpm。

令人惊讶的是,如果您在完全相同的顺序下再次运行测试步骤,您将能够读取敏感文件,无论其所有权和许可。

结论

从安全的角度来看,对于同一个 Nginx 网页服务器上的每个网站,使用 php-fpm 池具有不同的用户是必不可少的。

本文所描述的想法不是独一无二的,它存在于其他类似的PHP隔离技术中,如SuPHP。

Published At
Categories with 技术
comments powered by Disqus