Вот о чем речь:
<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:
- Открывает устройство /dev/ugidctl
- В процессе чтения конфигурационного файла собирает список необходимых пользователей
- Загружает этот список в ugidctl
- Получает случайный ключ от ядра для переключания между пользователями
Дочерний процесс:
- Сбрасывает права на основного пользователя
- Ждет запроса, смотрит какими правами его надо обслужить
- Используя ключ, полученный при инициализации, переключает пользователя
- Обрабатывает запрос
- Используя ключ, возвращает себе права основного пользователя
- 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 серверах это решение безотказно работает уже больше года.
Комментарии (22)
kelevra
30.10.2015 15:19-2во FreeBSD-порте апача (не только второго, но и первого) уже лет 10 изоляция реализована стартовым скриптом. всё остальное — это изобретения дырявых велосипедов.
004helix
30.10.2015 15:56Прямо какая-то космическая технология заставить MPM prefork для каждого виртуального хоста выбирать себе соответствующего пользователя. Да ещё и работает автоматически из стартового скрипта.
Вы читали статью?
jazzl0ver
30.10.2015 18:43Может кому-нибудь пригодится. Довольно давно (уже лет 10 как) решил похожую задачу (разрешить доступ через веб к репозиториям CVS только юзерам, имеющим туда доступ на уровне файловой системы) с помощью скрипта securecgi (http://sourceforge.net/projects/securecgi/). Сам скрипт суидный, но он запускается только для того, чтобы запустить заданный cgi-скрипт (в моему случае perl-скрипт) под id пользователя из переменной окружения REMOTE_USER. Так что даже, если cgi-скрипт окажется дырявым, система не окажется полностью скомпрометированной.
В то время пытался через список рассылки апача уговорить разработчиков реализовать такую функциональность к коре или через модуль, но поддержки у них моя инициатива не нашла. Аргументация была более чем странная (на мой взгляд): если взломают http-юзера, то смогут запустить любой suid- файл под этим юзером, а это — полшага до рута.
cvss
30.10.2015 19:33Хороший способ. Мы в нет.ру использовали похожую схему под freebsd 4 и apache 1, пока не перестали использовать эту платформу в 2011-м. Похожую вплоть до обмена куками (ключем) между ядром и апачем.
У вас даже лучше реализация тем, что сначала рутовый процесс передает в ядро список uid, на которые можно менять euid дочерних apache. В нашем случае использовался не список uid, а принадлежность диапазону выделенных для веб-пользователей uid.cvss
30.10.2015 19:52+1Но я бы хотел предостеречь от применения этой схемы при наличии стандартных, хоть и более сложных, альтернатив. Мы это делали только для работы mod_php с uid пользователя, так как не было выбора — во времена php3 и php4 не было ни php-fpm, ни других нормально работающих. Все остальные проблемы изоляции (запрет доступа к файлам других пользователей и запуск CGI/FCGI скриптов) решаются стандартными средствами unix и apache.
Для mod_perl и многих других интерпретаторов, встроенных в apache, изоляция через смену uid ничего не дает, так как пространство глобальных имен переменных является общим для всех пользователей. Соответственно, это обеспечивает и возможность доступа к чужим приложениям, и вероятность конфликта при использовании одинаковых имен.004helix
30.10.2015 22:26Согласен, в первую очередь моя реализация рассчитана для mod_php. В случае запуска приложений отдельно с помощью php-fpm, uwsgi и т.п. сильно возрастают накладные расходы на память, если виртуальных хостов много, а по одельности у них небольшая посещаемость. Плюс можно использовать общий opcache для php, например, что тоже хорошо экономит память.
xytop
А что скажете про mpm-itk?
Ну и неплохо было бы исходники на github выложить.
Ahcai5oh
Поддерживаю вопрос про небходимость велосипеда в свете существования MPM-ITK. А исходники обнаружить несложно:
https://ibuffed.com/pub/ugidctl/ugidctl-0.1.1-preview/
https://ibuffed.com/pub/ugidctl/mod_ugidctl-1.0.1-preview/
xytop
Несложно, но github вроде как стандарт уже. За архивы на малоизвестных сайтах уже давно не чествуют.
004helix
Отправил зеркала на гитхаб:
https://github.com/004helix/ugidctl
https://github.com/004helix/mod_ugidctl
Ahcai5oh
Это такой же «стандарт» как винда или андроид. То есть, не стандарт вообще. Пользуются многие, конечно, но навязывать (не путать с принуждением) не очень этично. У гитхаба есть несомненный плюс — найти нужный проект проще, чем где-то ещё, но этот плюс является следствием монополизации рынка. А ведь это даже не инструмент, а, по сути, коммерческий сервис. А решение выложить дерево исходников как есть на HTTP-сервере действительно несколько странное, но автору, видимо, для себя достаточно. Извиняюсь за оффтоп, конечно.
004helix
Тоже не понимаю, почему гитхаб это уже «стандарт». Ссылки на дерево исходников на http, кстати, я даже не публиковал. В статье просто ссылки на архивы с исходниками.
xytop
Открытые проекты принято размещать на специализированных хостингах.
Преимущество таких спецхостингов в том, что человек может не только код просмотреть онлайн, но и завести issue или прочитать документацию к проекту онлайн, подправить код или попросить добавить какие-то фичи. Это очень удобно, социализация во все поля :)
Раньше это были SourceForge, Google Code и т.д.
SourceForge себя скомпрометировал, Google Code закрылся и сам предложил перекинуть все проекты на Github.
Еще как вариант есть Bitbucket.
Вот тут можно прочитать про opensource best practices: opensource.com/business/14/9/community-best-practices-new-era-open-source
004helix
Обратите внимание на «Quirks and warnings» на их сайте:
Т.е. осноное — все дочерние процессы висят от рута. Второе — после обработки запроса процесс может только завершиться, т.к. не способен обрабытывать запросы к других хостам. Сильный удар по производительности, не говоря уже про keep-alive соединения, в котором последующие запросы просто невозможны, если идут на другой хост.
BAV_Lug
Тоже поддержу вопрос. Уже лет пять точно юзаю mpm-itk. Чем принципиально ваше решение лучше?
xytop
Вот прямо над вами ответили :)
mpm-itk делает switch на юзера только после парсинга запроса (т.е. в случае какого-то эксплойта для обработки запроса можно получить root), а эта библиотека делает всю обработку от юзера изначально.
equand
Есть же perser, оно и chroot умеет.
004helix
Полагаю вы имели ввиду peruser MPM. Проект скорее мертв чем жив. Да и большие проблемы с масшрабированием у него были.
equand
Да, через некоторое время процессы сегфолтятся, однако единственная проблема. Но всегд можно запускать несколько апачей на юзеров.
004helix
Там не только в сегфолтах проблемы, поверьте. Я там где-то даже в списке авторов поскакивал.
Конечно можно. Просто иногда запуск сотни апачей не лучший выход.