Изоляция виртуальных серверов в 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 серверах это решение безотказно работает уже больше года.

ссылка на оригинал статьи http://habrahabr.ru/post/269835/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *