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


目录
相关文章
|
27天前
|
应用服务中间件 PHP Apache
php搭建一个简单的网站
php搭建一个简单的网站
51 1
|
16天前
|
JavaScript PHP 数据安全/隐私保护
乞丐在线要饭系统PHP网站源码
在这个物欲横流、竞争激烈的时代,有时候我们真心觉得钱来得太不容易,甚至连最基本的生存都成了负担。于是,我们想出了一个特别“独特”的点子:用利息砸我,给我点施舍!
41 1
|
18天前
|
人工智能 搜索推荐 PHP
PHP在Web开发中的璀璨星辰:构建动态网站的幕后英雄###
【10月更文挑战第25天】 本文将带您穿越至PHP的宇宙,揭示其作为Web开发常青树的奥秘。通过生动实例与深入解析,展现PHP如何以简便、高效、灵活的姿态,赋能开发者打造动态交互式网站,同时不忘探讨其在新时代技术浪潮中面临的挑战与机遇,激发对技术创新与应用的无限思考。 ###
24 1
|
21天前
|
SQL 安全 Go
PHP在Web开发中的安全实践与防范措施###
【10月更文挑战第22天】 本文深入探讨了PHP在Web开发中面临的主要安全挑战,包括SQL注入、XSS攻击、CSRF攻击及文件包含漏洞等,并详细阐述了针对这些风险的有效防范策略。通过具体案例分析,揭示了安全编码的重要性,以及如何结合PHP特性与最佳实践来加固Web应用的安全性。全文旨在为开发者提供实用的安全指南,帮助构建更加安全可靠的PHP Web应用。 ###
32 1
|
23天前
|
Ubuntu PHP 开发者
如何在Ubuntu中切换多个PHP版本
通过上述步骤,您不仅能够高效地在Ubuntu系统中安装和切换PHP版本,还能根据项目需求灵活配置,大大提升开发效率与灵活性。更多关于服务器配置与优化的信息,获取全面的技术支持与解决方案。
25 1
|
25天前
|
前端开发 PHP 数据库
原生PHP网站源码
原生PHP网站通常指的是使用纯PHP代码编写的网站,没有使用框架或者类库来简化开发流程。
28 1
|
1月前
|
SQL 关系型数据库 MySQL
PHP与MySQL协同工作的艺术:开发高效动态网站
在这个后端技术迅速迭代的时代,PHP和MySQL的组合仍然是创建动态网站和应用的主流选择之一。本文将带领读者深入理解PHP后端逻辑与MySQL数据库之间的协同工作方式,包括数据的检索、插入、更新和删除操作。文章将通过一系列实用的示例和最佳实践,揭示如何充分利用这两种技术的优势,构建高效、安全且易于维护的动态网站。
|
1月前
|
tengine 应用服务中间件 Linux
Tengine、Nginx安装PHP命令教程
要在阿里云Linux上安装PHP,请先更新YUM源并启用PHP 8.0仓库,然后安装PHP及相关扩展。通过`php -v`命令验证安装成功后,需修改Nginx配置文件以支持PHP,并重启服务。最后,创建`phpinfo.php`文件测试安装是否成功。对于CentOS系统,还需安装EPEL源和Remi仓库,其余步骤类似。完成上述操作后,可通过浏览器访问`http://IP地址/phpinfo.php`测试安装结果。
|
1月前
|
Ubuntu Unix 应用服务中间件
Ubuntu16.04.1 安装Nginx
Ubuntu16.04.1 安装Nginx
|
1月前
|
存储 Kubernetes 负载均衡
基于Ubuntu-22.04安装K8s-v1.28.2实验(四)使用域名访问网站应用
基于Ubuntu-22.04安装K8s-v1.28.2实验(四)使用域名访问网站应用
28 1