Размещение нескольких сайтов на Nginx и Php-fpm в Ubuntu 14.04

Стек LEMP (Linux, nginx, MySQL, PHP) обеспечивает высокую скорость и надежность при запуске сайтов PHP. Однако такие его качества, как безопасность и изоляция, менее популярны среди пользователей.

В этом мануале вы узнаете больше о безопасности и изоляции при запуске сайтов на LEMP через разных пользователей. Это делается путем создания пулов php-fpm для каждого блока server nginx (виртуального хоста).

Требования

  • Сервер Ubuntu 14.04, настроенный по этому мануалу (общие инструкции сработают и на других дистрибутивах, но команды нужно откорректировать). Все команды в этом мануале нужно выполнять в сессии не-root пользователя.
  • Стек LEMP, установить который можно с помощью руководства Установка Linux, nginx, MySQL, PHP (LEMP stack) на Ubuntu 14.04.
  • FQDN, указывающий на ваш сервер. Если у вас нет FQDN, используйте условный site1.example.org. Откройте файл /etc/hosts в редакторе и добавьте эту строку (вместо site1.example.org укажите свой домен, если есть):

...
127.0.0.1 site1.example.org
...

Дополнительная безопасность стека LEMP

В общей настройке LEMP существует только один пул php-fpm, который запускает все скрипты PHP для всех сайтов под одним и тем же пользователем. Это создает две основные проблемы:

  1. Если веб-приложение в одном виртуальном хосте nginx (то есть субдомен или отдельный сайт) будет взломано, это затронет все сайты на этом сервере. Злоумышленник может читать конфигурации и данные БД других сайтов или даже изменять их файлы.
  2. Если вы хотите открыть пользователю доступ к одному из сайтов, размещенных на вашем сервере, вы фактически предоставите доступ ко всем сайтам. Например, у вас есть разработчик, он должен работать в промежуточной среде. Однако даже с очень ограниченными правами на доступ к файлам он все равно будет иметь доступ ко всем сайтам данного сервера, включая ваш основной сайт.

php-fpm решает эти проблемы с помощью индивидуальных пулов для каждого сайта.

1: Настройка php-fpm

Если вы выполнили все предварительные требования, на вашем сервере уже есть рабочий веб-сайт. Если у вас нет FQDN, вы можете получить доступ к нему по FQDN localhost локально или по IP-адресу сервера удаленно.

Теперь создайте второй сайт (site1.example.org) со своим собственным пулом php-fpm и пользователем Linux.

Для начала нужно создать пользователя. Для лучшей изоляции новый пользователь должен иметь свою собственную группу. Поэтому сначала создайте группу site1:

sudo groupadd site1

Затем создайте пользователя site1, который будет входить в эту группу.

sudo useradd -g site1 site1

На данный момент пользователь site1 не имеет пароля и не может входить на сервер. Если вам нужно предоставить кому-то доступ к сайту через этого пользователя, создайте для него пароль с помощью команды:

sudo passwd site1

Теперь пользователь может удаленно войти в систему через ssh или sftp.

Затем создайте новый пул php-fpm для site1. Пул php-fpm – это по сути обычный Linux-процесс, который работает под определенным пользователем/группой и прослушивает сокет Linux. Он также может прослушивать комбинацию IP:порт, но для этого потребуется больше ресурсов сервера, и это не самый предпочтительный метод.

По умолчанию в 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] – имя пула. Каждый пул должен иметь уникальное имя.
  • user и group – пользователь и группа Linux, в которой будет запущен новый пул.
  • listen – уникальное местоположение каждого пула.
  • listen.owner и listen.group определяют сокет нового пула php-fpm. Nginx должен иметь возможность читать этот сокет. Потому в сокете указываются пользователь и группа, через которые работает nginx (www-data).
  • php_admin_value позволяет настраивать пользовательские параметры php. Здесь эта строка используется для отключения функций, которые могут запускать команды Linux – exec, passthru, shell_exec, system.
  • php_admin_flag аналогичен параметру php_admin_value, но поддерживает только логические значения on и off. Отключите с его помощью функцию allow_url_fopen, которая позволяет сценариям PHP открывать удаленные файлы и может использоваться злоумышленниками.

Примечание: Вышеупомянутые значения php_admin_value и php_admin_flag также могут применяться глобально. Однако сайту они могут не понадобиться, поэтому по умолчанию они не настроены. преимущество пулов php-fpm заключается в том, что они позволяют точно настраивать индивидуальные параметры безопасности для каждого сайта. Кроме того, эти параметры могут использоваться с другими параметрами php для дальнейшей настройки среды сайта.

Параметры pm подробно рассматриваться здесь не будут, но вы должны знать, что они позволяют настраивать производительность пула.

Параметр chdir должен иметь значение /, что определяет корень файловой системы. Это значение не нужно изменять, если вы не используете другой важный параметр chroot.

Параметр chroot не включен в указанную выше конфигурацию. Это позволит вам запускать пул в закрытой среде, то есть заблокировать его внутри одного каталога. Это отлично подходит для настройки безопасности, поскольку вы можете заблокировать пул внутри корневого каталога веб-сайта. Однако такая настройка вызовет серьезные проблемы для любого 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.

Кроме того, нужно отключить кэширование php по умолчанию, предоставляемое opcache. Это расширение кэширования может улучшить производительность, но не безопасность, как вы убедитесь позже. Чтобы отключить его, отредактируйте файл /etc/php5/fpm/conf.d/05-opcache.ini с привилегиями суперпользователя и добавьте строку:

opcache.enable=0

После этого перезапустите php-fpm.

sudo service php5-fpm restart

2: Настройка nginx

Настроив пул php-fpm для второго сайта, вы можете приступать к настройке виртуального хоста nginx (для этого используются блоки server). Откройте файл/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;
}
}

Вышеприведенный код – это общая конфигурация блока server в nginx. Обратите внимание на выделенные красным части:

  • Root-каталог находится в /usr/share/nginx/sites/site1.
  • Имя сервера – это fqdn (например, site1.example.org).
  • fastcgi_pass определяет обработчика файлов php. Для каждого сайта нужно использовать индивидуальный unix-сокет (/var/run/php5-fpm-site1.sock).

Создайте корневой каталог:

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 по умолчанию и в его веб-root /usr/share/nginx/html/. Для этого введите:

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

Затем скопируйте файл в корневой каталог другого сайта (site1.example.org):

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

Теперь вы можете выполнить базовый тест, чтобы проверить работу сервера. Тест можно выполнить с помощью браузера, терминала или lynx, браузера командной строки. Если у вас еще нет 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

Аналогично выполняется тест второго сайта:

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

Если вы добавили какие-либо пользовательские настройки php для каждого пула php-fpm, вы также можете проверить их значения указанным выше способом, фильтруя интересующий вас результат.

Итак, вы знаете, что два сайта работают под разными пользователями. Теперь нужно защитить соединение. Чтобы продемонстрировать проблему безопасности, которую устраняет этот мануал, создайте файл с конфиденциальной информацией. Обычно такой файл содержит данные для подключения к БД. Если кто-либо узнает эту информацию, он сможет навредить сайту.

Создайте файл /usr/share/nginx/html/config.php. Добавьте в него строку:

<?php
$pass = 'secret';
?>

В вышеприведенном файле определяется переменная pass, которая хранит значение secret. Естественно, необходимо ограничить доступ к этому файлу, поэтому нужно установить права 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);
?>

Измените права на файл:

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

Чтобы убедиться, что права собственности и доступа установлены правильно, запустите команду:

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/

Чтобы сохранить отношения сайт/пользователь, убедитесь, что на каждом сайте файлы принадлежат соответствующему пользователю. Для этого измените права собственности на недавно скопированный файл с помощью команды:

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

Чтобы убедиться, что права на файл установлены правильно, просмотрите содержимое корневого каталога site1 с помощью команды:

ls -l /usr/share/nginx/sites/site1/

Вы увидите:

-rw-r--r-- 1 site1 site1  80 Jun 21 16:44 readfile.php

Теперь попробуйте получить доступ к файлу site1.example.com с помощью команды:

lynx --dump http://site1.example.org/readfile

Команда вернет пустой вывод. Кроме того, если вы будете искать ошибки в логах 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

Примечание: Подобную ошибку вы увидите также в выводе lynx, если параметр display_errors имеет значение On в конфигурационном файле php-fpm, /etc/php5/fpm/php.ini.

Предупреждение показывает, что скрипт с сайта site1.example.org не может прочитать конфиденциальный файл config.phpfrom основного сайта. Таким образом, сайты, которые работают под разными пользователями, не могут поставить под угрозу безопасность друг друга.

Ранее вы отключили кэширование по умолчанию, предоставляемое opcache. Если вам интересно, попробуйте снова включить opcache с привилегиями суперпользователя в файле /etc/php5/fpm/conf.d/05-opcache.ini (строка opcache.enable=) и перезапустите php5-fpm с помощью команды:

sudo service php5-fpm restart

Если вы повторите тестирование с самого начала, вы сможете прочитать конфиденциальный файл независимо от прав доступа к нему.

Заключение

Для обеспечения безопасности ваших сайтов необходимо использовать индивидуальные пулы php-fpm для каждого сайта на веб-сервере Nginx. Такая изоляция может немного снизить производительность, но это позволяет предотвратить серьезные атаки и устранить уязвимости.

Подобным образом можно использовать и другие технологии изоляции PHP, например, SuPHP. Однако производительность всех других средств намного хуже, чем у php-fpm.

Tags: , ,

1 комментарий

  • vebmaster says:

    > Если вы повторите тестирование с самого начала, вы сможете прочитать конфиденциальный файл независимо от прав доступа к нему.

    Это для какой версии php? Попробовал такой пример для php7.2, файл не прочитался.

Добавить комментарий