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

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

简介

众所周知,LEMP 栈(Linux、nginx、MySQL、PHP)为运行 PHP 站点提供了无与伦比的速度和可靠性。不过,该流行栈的其他优势,如安全性和隔离性,却不太为人所知。

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

先决条件

本指南已在 Ubuntu 14.04 上进行了测试。所述的安装和配置在其他操作系统或操作系统版本上可能类似,但命令和配置文件的位置可能会有所不同。

它还假定您已经设置了 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 文件,添加以下行(如果您使用它,请将 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] 是池的名称。对于每个池,您必须指定一个唯一的名称。
  • usergroup 分别代表新池将在其下运行的 Linux 用户和组。
  • listen 应该指向每个池的唯一位置。
  • listen.ownerlisten.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 脚本打开远程文件,可能会被攻击者利用。

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

chdir 选项应该是 /,即文件系统的根目录。除非您使用另一个重要选项 chroot,否则不应更改此选项。

故意未在上述配置中包含选项 chroot。它将允许您在受限环境中运行池,即锁定在一个目录中。这对于安全性很好,因为您可以将池锁定在站点的 Web 根目录中。但是,这种终极安全性将为依赖于系统二进制文件和应用程序(如 Imagemagick)的任何体面的 PHP 应用程序带来严重问题。如果您对此话题感兴趣,请阅读文章《如何使用 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(); ?>。您首先需要将此文件放在默认的 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/

现在,您已经准备好运行最基本的测试以验证服务器用户。您可以使用浏览器或 Droplet 终端和命令行浏览器 lynx 执行测试。如果您的 Droplet 上尚未安装 lynx,请使用命令 sudo apt-get install lynx 进行安装。

首先检查默认站点的 info.php 文件。它应该可以在本地主机上访问,命令如下:

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

然后尝试从 site1.example.com 访问相同的文件,命令为 lynx --dump http://site1.example.org/readfile.php。您将只看到返回的空格。此外,如果您使用 grep 命令在 nginx 的错误日志中搜索错误,命令为 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

警告显示,来自 site1.example.org 站点的脚本无法读取主站点的敏感文件 config.php。因此,运行在不同用户下的站点不会危及彼此的安全性。

如果您回到本文的配置部分末尾,您将看到我们已禁用了 opcache 提供的默认缓存。如果您好奇为什么,请尝试通过使用超级用户权限在文件 /etc/php5/fpm/conf.d/05-opcache.ini 中设置 opcache.enable=1,然后使用命令 sudo service php5-fpm restart 重新启动 php5-fpm 来重新启用 opcache。

令人惊讶的是,如果您按照完全相同的顺序再次运行测试步骤,您将能够读取敏感文件,而不管其所有权和权限如何。这个 opcache 中的问题已经报告了很长时间,但截至本文撰写时,它尚未被修复。

结论

从安全的角度来看,对于在同一台 Nginx web 服务器上的每个站点使用不同用户的 php-fpm 池是至关重要的。即使这会带来一些性能损失,但这种隔离的好处可以防止严重的安全漏洞。

本文描述的思想并不是独一无二的,在其他类似的 PHP 隔离技术中也存在,比如 SuPHP。然而,所有其他替代方案的性能都远远不如 php-fpm。


目录
相关文章
|
6月前
|
Ubuntu 网络协议 应用服务中间件
在 Ubuntu 上安装 Nginx
在 Ubuntu 上安装和配置 Nginx 非常简单。首先更新系统包,然后通过 `apt` 安装 Nginx,检查服务状态并配置防火墙规则。访问服务器 IP 测试是否成功显示默认页面。还可管理服务、创建虚拟主机及排查常见问题,适合新手快速上手部署高性能 Web 服务。
727 0
|
4月前
|
安全 PHP 数据库
PHP中的陷阱:字符串与数字比较时,你真的安全吗?
PHP中的陷阱:字符串与数字比较时,你真的安全吗?
|
7月前
|
Ubuntu PHP
Ubuntu下使用apt为Apache2编译PHP7.1
以上就是在Ubuntu系统下,使用apt为Apache2编译PHP7.1的过程。希望这个过程对你有所帮助,如果你在执行过程中遇到任何问题,都可以在网上找到相关的解决方案。
136 25
|
7月前
|
Ubuntu PHP Apache
在Ubuntu系统中为apt的apache2编译PHP 7.1的方法
以上就是在Ubuntu系统中为apt的apache2编译PHP 7.1的方法。希望这个指南能帮助你成功编译PHP 7.1,并在你的Apache服务器上运行PHP应用。
179 28
|
8月前
|
Ubuntu PHP 数据库
|
11月前
|
负载均衡 Ubuntu 应用服务中间件
nginx修改网站默认根目录及发布(linux、centos、ubuntu)openEuler软件源repo站点
通过合理配置 Nginx,我们可以高效地管理和发布软件源,为用户提供稳定可靠的服务。
1022 13
|
12月前
|
SQL 安全 PHP
PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全
本文深入探讨了PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全。
575 4
|
SQL 安全 Go
PHP在Web开发中的安全实践与防范措施###
【10月更文挑战第22天】 本文深入探讨了PHP在Web开发中面临的主要安全挑战,包括SQL注入、XSS攻击、CSRF攻击及文件包含漏洞等,并详细阐述了针对这些风险的有效防范策略。通过具体案例分析,揭示了安全编码的重要性,以及如何结合PHP特性与最佳实践来加固Web应用的安全性。全文旨在为开发者提供实用的安全指南,帮助构建更加安全可靠的PHP Web应用。 ###
251 1
|
Ubuntu PHP 开发者
如何在Ubuntu中切换多个PHP版本
通过上述步骤,您不仅能够高效地在Ubuntu系统中安装和切换PHP版本,还能根据项目需求灵活配置,大大提升开发效率与灵活性。更多关于服务器配置与优化的信息,获取全面的技术支持与解决方案。
330 1
|
Docker 容器
docker nginx-proxy 添加自定义https网站
docker nginx-proxy 添加自定义https网站
159 4