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

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 22

    +1
    А что скажете про mpm-itk?
    Ну и неплохо было бы исходники на github выложить.
      0
      Поддерживаю вопрос про небходимость велосипеда в свете существования MPM-ITK. А исходники обнаружить несложно:
      https://ibuffed.com/pub/ugidctl/ugidctl-0.1.1-preview/
      https://ibuffed.com/pub/ugidctl/mod_ugidctl-1.0.1-preview/
        0
        Несложно, но github вроде как стандарт уже. За архивы на малоизвестных сайтах уже давно не чествуют.
          0
          Отправил зеркала на гитхаб:
          https://github.com/004helix/ugidctl
          https://github.com/004helix/mod_ugidctl
            +2
            Это такой же «стандарт» как винда или андроид. То есть, не стандарт вообще. Пользуются многие, конечно, но навязывать (не путать с принуждением) не очень этично. У гитхаба есть несомненный плюс — найти нужный проект проще, чем где-то ещё, но этот плюс является следствием монополизации рынка. А ведь это даже не инструмент, а, по сути, коммерческий сервис. А решение выложить дерево исходников как есть на HTTP-сервере действительно несколько странное, но автору, видимо, для себя достаточно. Извиняюсь за оффтоп, конечно.
              0
              Тоже не понимаю, почему гитхаб это уже «стандарт». Ссылки на дерево исходников на http, кстати, я даже не публиковал. В статье просто ссылки на архивы с исходниками.
                0
                Открытые проекты принято размещать на специализированных хостингах.
                Преимущество таких спецхостингов в том, что человек может не только код просмотреть онлайн, но и завести issue или прочитать документацию к проекту онлайн, подправить код или попросить добавить какие-то фичи. Это очень удобно, социализация во все поля :)
                Раньше это были SourceForge, Google Code и т.д.
                SourceForge себя скомпрометировал, Google Code закрылся и сам предложил перекинуть все проекты на Github.
                Еще как вариант есть Bitbucket.
                Вот тут можно прочитать про opensource best practices: opensource.com/business/14/9/community-best-practices-new-era-open-source
          +3
          Обратите внимание на «Quirks and warnings» на их сайте:
          Since mpm-itk has to be able to setuid(), it runs as root (although restricted with POSIX capabilities and seccomp v2 where possible) until the request is parsed and the vhost determined. This means that any code execution hole before the request is parsed will be a potential root security hole. (The most likely place is probably in mod_ssl.) This is not likely to change in the near future, as socket passing, the most likely alternative solution, is very hard to get to work properly in a number of common use cases (e.g. SSL).

          Т.е. осноное — все дочерние процессы висят от рута. Второе — после обработки запроса процесс может только завершиться, т.к. не способен обрабытывать запросы к других хостам. Сильный удар по производительности, не говоря уже про keep-alive соединения, в котором последующие запросы просто невозможны, если идут на другой хост.
            0
            Тоже поддержу вопрос. Уже лет пять точно юзаю mpm-itk. Чем принципиально ваше решение лучше?
              0
              Вот прямо над вами ответили :)
              mpm-itk делает switch на юзера только после парсинга запроса (т.е. в случае какого-то эксплойта для обработки запроса можно получить root), а эта библиотека делает всю обработку от юзера изначально.
                0
                Есть же perser, оно и chroot умеет.
                  0
                  Полагаю вы имели ввиду peruser MPM. Проект скорее мертв чем жив. Да и большие проблемы с масшрабированием у него были.
                    0
                    Да, через некоторое время процессы сегфолтятся, однако единственная проблема. Но всегд можно запускать несколько апачей на юзеров.
                      0
                      Да, через некоторое время процессы сегфолтятся, однако единственная проблема.

                      Там не только в сегфолтах проблемы, поверьте. Я там где-то даже в списке авторов поскакивал.

                      Но всегд можно запускать несколько апачей на юзеров.

                      Конечно можно. Просто иногда запуск сотни апачей не лучший выход.
            –2
            во FreeBSD-порте апача (не только второго, но и первого) уже лет 10 изоляция реализована стартовым скриптом. всё остальное — это изобретения дырявых велосипедов.
              0
              Прямо какая-то космическая технология заставить MPM prefork для каждого виртуального хоста выбирать себе соответствующего пользователя. Да ещё и работает автоматически из стартового скрипта.
              Вы читали статью?
              0
              Может кому-нибудь пригодится. Довольно давно (уже лет 10 как) решил похожую задачу (разрешить доступ через веб к репозиториям CVS только юзерам, имеющим туда доступ на уровне файловой системы) с помощью скрипта securecgi (http://sourceforge.net/projects/securecgi/). Сам скрипт суидный, но он запускается только для того, чтобы запустить заданный cgi-скрипт (в моему случае perl-скрипт) под id пользователя из переменной окружения REMOTE_USER. Так что даже, если cgi-скрипт окажется дырявым, система не окажется полностью скомпрометированной.

              В то время пытался через список рассылки апача уговорить разработчиков реализовать такую функциональность к коре или через модуль, но поддержки у них моя инициатива не нашла. Аргументация была более чем странная (на мой взгляд): если взломают http-юзера, то смогут запустить любой suid- файл под этим юзером, а это — полшага до рута.
                0
                Для безопасного запуска cgi-скриптов в apache давно есть suexec
                  0
                  suexec — отличный механизм. проблема в том, что он может работать только под тем юзером, который жестко прописан в конфиге апача. динамически становиться нужным юзером он не умеет.
                0
                Хороший способ. Мы в нет.ру использовали похожую схему под freebsd 4 и apache 1, пока не перестали использовать эту платформу в 2011-м. Похожую вплоть до обмена куками (ключем) между ядром и апачем.

                У вас даже лучше реализация тем, что сначала рутовый процесс передает в ядро список uid, на которые можно менять euid дочерних apache. В нашем случае использовался не список uid, а принадлежность диапазону выделенных для веб-пользователей uid.
                  +1
                  Но я бы хотел предостеречь от применения этой схемы при наличии стандартных, хоть и более сложных, альтернатив. Мы это делали только для работы mod_php с uid пользователя, так как не было выбора — во времена php3 и php4 не было ни php-fpm, ни других нормально работающих. Все остальные проблемы изоляции (запрет доступа к файлам других пользователей и запуск CGI/FCGI скриптов) решаются стандартными средствами unix и apache.

                  Для mod_perl и многих других интерпретаторов, встроенных в apache, изоляция через смену uid ничего не дает, так как пространство глобальных имен переменных является общим для всех пользователей. Соответственно, это обеспечивает и возможность доступа к чужим приложениям, и вероятность конфликта при использовании одинаковых имен.
                    0
                    Согласен, в первую очередь моя реализация рассчитана для mod_php. В случае запуска приложений отдельно с помощью php-fpm, uwsgi и т.п. сильно возрастают накладные расходы на память, если виртуальных хостов много, а по одельности у них небольшая посещаемость. Плюс можно использовать общий opcache для php, например, что тоже хорошо экономит память.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое