如何在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。


目录
相关文章
|
10天前
|
PHP
PHP全自动采集在线高清壁纸网站源码
PHP全自动采集在线高清壁纸网站源码,PHP全自动采集在线高清壁纸网站源码,一款开源壁纸源码,无需安装。集合360壁纸,百度壁纸,必应壁纸,简单方便。每天自动采集,自动更新,非常不错,php源码 网站源码 免费源码 自动采集。
23 3
|
10天前
|
PHP 数据库
2024表白墙PHP网站源码
2024表白墙PHP网站源码
23 1
|
28天前
|
缓存 Ubuntu 前端开发
在Ubuntu上手动与自动启动Nginx的踩坑经历、以及重启服务
本文分享了作者在Ubuntu系统上手动和自动启动Nginx服务的踩坑经历,包括创建启动脚本、解决依赖问题、配置服务自动启动以及通过命令行管理Nginx服务的方法。
131 0
在Ubuntu上手动与自动启动Nginx的踩坑经历、以及重启服务
|
13天前
|
前端开发 关系型数据库 MySQL
最新黑名单查询录入系统PHP网站源码
最新黑名单查询录入系统PHP网站源码 前端html 后端layui 操作部分都采用API接口的方式实线 集结了layui表格的多数据操作,添加,批量删除,分页,单项删除 后台数据修改采用绑定参数的形式来进行修改可以很好的预防数据库注入,当然如果你想要测试这个防注入的你也可以尝试一下 PHP版本70+ 数据库Mysql 5.6 上传程序访问 http://你的域名/install 安装
34 0
|
14天前
|
前端开发 安全 JavaScript
PHP与现代Web开发:探索PHP在构建动态网站中的角色和优势
【8月更文挑战第29天】 在数字时代的浪潮下,PHP以其独特的灵活性、易用性以及强大的社区支持,持续成为Web开发领域的重要力量。本文将深入探讨PHP如何适应现代Web开发的需求,通过具体示例揭示PHP的实际应用,并分析其在面对新兴技术挑战时的应对策略。我们将一探究竟,PHP如何在众多编程语言中脱颖而出,成为许多开发者和企业的首选。
|
18天前
|
PHP
【Azure Developer】PHP网站使用AAD授权登录的参考示例
【Azure Developer】PHP网站使用AAD授权登录的参考示例
|
19天前
|
安全 应用服务中间件 网络安全
Nginx要怎么配置才算安全
Nginx要怎么配置才算安全
35 0
|
21天前
|
应用服务中间件 Linux PHP
Linux搭建tengine2.0<Nginx>+php7环境
本文介绍了在Linux系统上搭建Tengine 2.0(一个Nginx的增强版本)和PHP 7环境的详细步骤,包括创建安装目录、下载源码包及依赖库、编译安装Nginx、配置Nginx、安装PHP及其依赖、设置PHP-FPM、配置环境变量、安装Git和Composer,以及服务管理和日志查看等。
45 0
|
28天前
|
关系型数据库 MySQL 应用服务中间件
在Ubuntu 16.04上使用Nginx安装和保护phpMyAdmin的方法
在Ubuntu 16.04上使用Nginx安装和保护phpMyAdmin的方法
17 0
|
28天前
|
关系型数据库 Linux 应用服务中间件
如何在 Ubuntu 14.04 服务器上使用 Nginx 安装和保护 phpMyAdmin
如何在 Ubuntu 14.04 服务器上使用 Nginx 安装和保护 phpMyAdmin
14 0