[Из песочницы] Изоляция виртуальных серверов в apache2 — ugidctl

Некоторое время назад я сделал для себя решение, которое позволяет эффективно изолировать процессы apache2. Теперь он может обрабатывать каждый запрос от имени своего системного пользователя. Сегодня я хочу поделиться этим решением.

Вот о чем речь:

<VirtualHost *:80>
    ServerName host1.example.com
    ServerAdmin webmaster1@example.com
    ServerUserGroup user1 group1
    DocumentRoot /var/www/host1
</VirtualHost>
<VirtualHost *:80>
    ServerName host2.example.com
    ServerAdmin webmaster2@example.com
    ServerUserGroup user2 group2
    DocumentRoot /var/www/host2
</VirtualHost>


При этом корневые директории виртуальных хостов могут быть доступны только соответсвующим пользователям:

# ls -la /var/www
total 16
drwxr-xr-x   4 root  root   4096 Oct 26 16:10 .
drwxr-xr-x  21 root  root   4096 Oct 26 01:13 ..
drwxr-x---   2 user1 group1 4096 Oct 26 16:10 host1
drwxr-x---   2 user1 group2 4096 Oct 26 16:10 host2


Это не очередные танцы с бубном вогруг многопоточности, запуска процессов от рута и т.п. Основная идея в том, чтобы процесс самостоятельно решил, с какими правами ему необходимо обработать запрос, взял себе эти права, обработал, и снова вернул себе права основного пользователя apache.
При этом я хотел, чтобы apache изначально был ограничен списком пользователей, которые описаны в его конфигурации при запуске, а во время обработки запроса было бы крайне сложно (либо практически невозможно) процессу «взломать» механизм переключения и присвоить себе чужие права.

Основное применение для меня — это linux, но существующих механизмов в его ядре не хватает, чтобы реализовать мою идею только в окружении пользователя. Поэтому решение состоит из 2-х частей — модуль ядра и модуль apache. Модуль ядра предоставляет устройство /dev/ugidctl, через которое можно системным вызовом ioctl(2) управлять списками разрешенных пользователей для процесса в привилегированном режиме, и, собственно, переключать пользователей в непривелигерованном режиме. Со стороны apache этим механизмом пользуется другой модуль, который реализует весь этот фунционал.

Чуть более подробно алгоритм работы следующий:

Корневой процесс apache:

  1. Открывает устройство /dev/ugidctl
  2. В процессе чтения конфигурационного файла собирает список необходимых пользователей
  3. Загружает этот список в ugidctl
  4. Получает случайный ключ от ядра для переключания между пользователями


Дочерний процесс:

  1. Сбрасывает права на основного пользователя
  2. Ждет запроса, смотрит какими правами его надо обслужить
  3. Используя ключ, полученный при инициализации, переключает пользователя
  4. Обрабатывает запрос
  5. Используя ключ, возвращает себе права основного пользователя
  6. goto 2


В случае взлома дочернего процесса, даже при получении ключа для переключений, ограничения списком пользователей не позволят злоумышленнику повысить привилегии в системе. Как максимум — можно будет залезть в любую корневую директорию любого виртуального хоста, что равносильно тому, как немодифицированный apache с MPM prefork обрабатывает все запросы. При этом сложность нахождения ключа в памяти дочернего процесса аналогична сложности нахождения приватных ключей в памяти модуля mod_ssl.

Естественно, согласно всей моей идее, модуль рассчитан только на работу с MPM prefork. Также стоит по возможности избегать многопоточности при обработке запросов внутри процесса apache — мой модуль переключает права только основной нити процесса.

Скачать можно тут:

Модуль ядра
Модуль apache

Небольшая инструкция для сборки на CentOS 7:

Ставим необходимые для сборки пакеты:

[root@el7 ~]# yum install -y gcc httpd-devel kernel-devel


Скачиваем и распаковываем исходники:

[root@el7 ~]# wget -q -O - https://ibuffed.com/pub/ugidctl/ugidctl-0.1.1.tar.gz | tar -xz
[root@el7 ~]# wget -q -O - https://ibuffed.com/pub/ugidctl/mod_ugidctl-1.0.1.tar.gz | tar -xz


Собираем и загружаем модуль ядра:

[root@el7 ~]# cd ~/ugidctl-0.1.1/
[root@el7 ugidctl-0.1.1]# make
[root@el7 ugidctl-0.1.1]# insmod ugidctl.ko


Собираем и загружаем модуль apache2:

[root@el7 ~]# cd ~/mod_ugidctl-1.0.1/
[root@el7 mod_ugidctl-1.0.1]# apxs -i -c mod_ugidctl.c 
[root@el7 mod_ugidctl-1.0.1]# echo 'LoadModule ugidctl_module modules/mod_ugidctl.so' > /etc/httpd/conf.modules.d/99-ugidctl.conf
[root@el7 mod_ugidctl-1.0.1]# systemctl restart httpd


Или можно воспользоваться готовыми сборками для el6 (RHEL 6u2 + / CentOS 6.2 +, только x86_64) и для el7 (RHEL 7 / CentOS 7). На нескольких el6 серверах это решение безотказно работает уже больше года.

© Habrahabr.ru