(925)500-70-62
  IT-поддержка, компьютеры, разработка ПО
  
тех.статьи
chroot безопасность


Оригинал статьи на http://slackware.tomsk.ru/docs/?p=chroot

Что это такое?

      Данная статья посвящена одному из приемов усиления безопасности функционирования сервисов в Linux системах - помещению их в chrooted environment (далее - "CE"). CE - это, по сути, такое программное окружение, в котором приложение считает корневым каталогом какой-то отдельный (с точки зрения основной системы) каталог.

      Термин "chrooted environment" происходит от имени системного вызова chroot() и соответствующей утилиты chroot, которая производит запуск программы, имя которой передается ей в качестве аргумента, в таком окружении с измененным корневым каталогом, путь к которому также передается в качестве аргумента. Программа, запущенная в CE, ограничена в доступе к файловой системе его рамками. Во FreeBSD существует подобный подход к изоляции программ - jail (тюрьма), что более образно описывает ее назначение - целевое приложение оказывается в программной "тюрьме", из которой достаточно проблематично выбраться. Широко распространенным примером CE являются FTP-сервера, большинство из которых по умолчанию работают с chroot - клиент, зашедший на сервер, не может увидеть файлы выше каталога, назначенного администратором в качестве корневого.

Зачем это нужно?

      Например, на вашем сервере есть несколько сервисов, и вам хотелось бы изолировать их друг от друга и от основной системы, чтобы спать спокойнее - ведь не бывает идеальных программ, и всегда найдется уязвимость, которую решит использовать злобный взломщик или просто скучающий script kiddie с новым эксплоитом. Ситуация с множеством сервисов на одной Linux-машине чрезвычайно распространена в небольших сетях - там сервер, изначально задумывавшийся как просто пограничный маршрутизатор локальной сети, постепенно обрастает функциями - на него свешивают почту, веб-сервер, базу данных, FTP-сервер, файловый сервер - благо, при небольшой нагрузке это все благополучно может работать на одной-единственной железке скромной конфигурации...

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

      Кстати, некоторые сервисы изначально подразумевают запуск в CE - например, распространенный DNS-сервер BIND создатели рекомендуют эксплуатировать в CE (создание окружения для нормальной работы этого сервиса подробно описано в официальной документации на BIND), при этом сам демон совершает системный вызов chroot(), если ему указать в качестве аргумента расположение корневого каталога CE. Аналогичная ситуация с MySQL - в конфигурационном файле указывается расположение подготовленного окружения для сервиса, демон при запуске помимо смены UID/GID, также вызывает chroot(). Те сервисы, в которых не предусмотрена работа в CE "из коробки", можно запускать с помощью уже упоминавшейся утилиты chroot.

Базовые принципы

      Итак, приступим к детальному разбору того, как этот метод работает. Сразу необходимо отметить, что делать системный вызов chroot() может только root. Это обусловлено рядом факторов - в частности, злоумышленник мог бы, имея только пользовательские привилегии, обмануть setuid программу, поместив ее в специально подготовленный CE с подтасованным passwd файлом, что позволило бы ему поднять привилегии.

      Основные проблемы при создании chrooted среды создает именно тот факт, что приложение "не видит" никаких файлов за рамками своего корневого каталога. В общем - сильная сторона оказывается одновременно и слабой, как это часто бывает в этой жизни. Из этого обстоятельства вытекает тот факт, что если для нормальной работы программы нужны какие-то библиотеки и дополнительные файлы - их придется скопировать внутрь CE. Если с библиотеками вопрос в ряде случаев можно решить, статически слинковав искомое приложение и получить его в виде одного большого бинарного файла, то с остальными файлами этот трюк не пройдет. Для того, чтобы создать оптимальную изолированную "камеру" для сервиса, мы должны достаточно хорошо знать его внутреннее устройство, чтобы скопировать все нужное, не прихватив при этом в CE лишнего, т.к. жесткие диски у вас наверняка не бесконечного объема. Частично эту проблему помогают решить инструменты, входящие практически в любой современный дистрибутив Linux.

Инструментарий

      Вот они, наши основные помощники по помещению приложения в "тюрьму":

  • chroot - основная утилита, описанная выше, делает системный вызов chroot()
  • chrootuid - функционально отличается от просто chroot лишь тем, что может еще и менять UID процесса посредствов setuid() - эта утилита может быть полезной, если вы хотите, чтобы сервис работал не от root, а его авторы не предусмотрели такой возможности (может отсутствовать в вашем дистрибутиве)
  • ldd - выводит информацию о зависимостях от общедоступных (shared) библиотек
  • strace - позволяет отслеживать системные вызовы и сигналы, чрезвычайно мощная утилита
  • практический опыт - чем больше, тем лучше

      Если помещенное в CE приложение не работает или работает не так, как нужно, то алгоритм отладки такой:

  1. с помощью ldd выясняем, какие из библиотек требуются, и копируем их в CE (естественно, сохраняя структуру каталогов)
  2. внимательно смотрим вывод программы и лог-файлы - часто запускаемый сервис сам говорит, что ему не хватает для работы
  3. анализируем лог-файлы сервиса, лежащие уже внутри CE (если таковые имеются)
  4. запускаем программу через strace и сохраняем вывод STDOUT в файл для дальнейшего анализа:
    strace chroot /chroot/service /path/to/service 2> ./strace.log
    
  5. проверяем права доступа на файлы и каталоги внутри CE
  6. в случае неудачи - пытаемся еще раз пройтись по этой цепочке, думаем, советуемся со знакомым гуру, идем гуглить и совершаем прочие эзотерические действия.

      Помимо уже упомянутых утилит, существуют и более продвинутые инструментальные решения по созданию CE, например - jailkit (работающий, кстати, не только на Linux, но и на FreeBSD, OpenBSD и MacOSX). Однако, по сути, этот пакет лишь делает за вас часть рутины, в любом случае пригодится знание того, как собрать CE руками.

      Еще один практический совет - автоматизируйте свой труд, создавайте shell-скрипты генерации CE, постепенно дописывая их по ходу мигрирования сервиса в CE - тем самым вы избавите себя от ненужной механической работы и снизите вероятность ошибки.

Практический пример: Apache

      Наверное, один из наиболее часто помещаемых в CE сервисов - веб-сервер Apache. Про это много написано и под разными углами, но я все же рискну повториться. Рассмотрим пошагово перенос в CE программной связки Apache 1.3.x + PHP 5 с учетом взаимодействия с СУБД MySQL (классический комплекс, именуемый в народе "LAMP" - Linux + Apache + MySQL + PHP). В примере предполагается, что мы имеем дело с дистрибутивной сборкой Apache в Slackware (для вашей системы файлы могут быть расположены немного по-другому, но принцип должен быть понятен).

      Вот скрипт для автоматизированного создания CE:

#!/bin/sh
# create chrooted environment for Apache 1.3 + PHP 5
# Den aka Diesel <diesel@sherdart.net>

PREFIX=/chroot/httpd
APACHE_PREFIX=/usr
PHP_PREFIX=/usr
APACHE_DATADIR=/var/www

# создаем структуру каталогов:

mkdir -p $PREFIX
mkdir -p $PREFIX/dev
mkdir -p $PREFIX/etc
mkdir -p $PREFIX/lib
mkdir -p $PREFIX/tmp
mkdir -p $PREFIX/var/cache/proxy
mkdir -p $PREFIX/var/run
mkdir -p $PREFIX/$APACHE_PREFIX/bin
mkdir -p $PREFIX/$APACHE_PREFIX/lib
mkdir -p $PREFIX/$APACHE_PREFIX/sbin
mkdir -p $PREFIX/$APACHE_PREFIX/libexec
mkdir -p $PREFIX/var/log/apache
mkdir -p $PREFIX/var/run/mysql
mkdir -p $PREFIX/$PHP_PREFIX/lib/php
mkdir -p $PREFIX/home

# выставляем 'sticky bit' на /tmp:

chmod 1777 $PREFIX/tmp

# создаем файл устройства /dev/null:

mknod $PREFIX/dev/null c 1 3
chmod 666 $PREFIX/dev/null
chown root:sys $PREFIX/dev/null

# копируем конфигурационные файлы:

cat /etc/group|egrep "apache:|nobody:|nogroup:" > $PREFIX/etc/group
cat /etc/passwd|egrep "apache:|nobody:" > $PREFIX/etc/passwd
cat /etc/shadow|egrep "apache:|nobody:" > $PREFIX/etc/shadow
chmod 640 $PREFIX/etc/shadow
cp /etc/HOSTNAME $PREFIX/etc
cp /etc/host.conf $PREFIX/etc
cp /etc/hosts $PREFIX/etc
cp /etc/nsswitch.conf $PREFIX/etc
cp /etc/resolv.conf $PREFIX/etc
cp /etc/localtime $PREFIX/etc
cp -R /etc/apache $PREFIX/etc

# копируем данные и лог-файлы:

cp -R $APACHE_DATADIR $PREFIX`dirname $APACHE_DATADIR`
cp -R /var/log/apache $PREFIX/var/log

# копируем бинарные файлы:

cp $APACHE_PREFIX/bin/htdigest $PREFIX/$APACHE_PREFIX/bin
cp $APACHE_PREFIX/bin/mm-config $PREFIX/$APACHE_PREFIX/bin
cp $APACHE_PREFIX/bin/htpasswd $PREFIX/$APACHE_PREFIX/bin
cp $APACHE_PREFIX/bin/checkgid $PREFIX/$APACHE_PREFIX/bin
cp $APACHE_PREFIX/bin/dbmmanage $PREFIX/$APACHE_PREFIX/bin
cp $APACHE_PREFIX/sbin/ab $PREFIX/$APACHE_PREFIX/sbin
cp $APACHE_PREFIX/sbin/apxs $PREFIX/$APACHE_PREFIX/sbin
cp $APACHE_PREFIX/sbin/httpd $PREFIX/$APACHE_PREFIX/sbin
cp $APACHE_PREFIX/sbin/logresolve $PREFIX/$APACHE_PREFIX/sbin
cp $APACHE_PREFIX/sbin/rotatelogs $PREFIX/$APACHE_PREFIX/sbin
cp $APACHE_PREFIX/sbin/apachectl-standard $PREFIX/$APACHE_PREFIX/sbin
cp $APACHE_PREFIX/sbin/apacheconfig $PREFIX/$APACHE_PREFIX/sbin
cp -R $APACHE_PREFIX/libexec/apache $PREFIX/$APACHE_PREFIX/libexec

# копируем библиотеки:

for BINARY in "$APACHE_PREFIX/sbin/httpd
$APACHE_PREFIX/libexec/apache/libphp5.so
$APACHE_PREFIX/lib/php/extensions/mysql.so"; do
    for i in `ldd $BINARY|awk '{print $3}'`; do
        d=$(dirname $i)
        if [[ $d != .* ]]; then mkdir -p $PREFIX$d; fi
        if [[ $d != .* ]]; then cp $i $PREFIX$d; fi
    done
done
cp -d /lib/libnss_compat* $PREFIX/lib
cp -d /lib/libnss_dns* $PREFIX/lib
cp -d /lib/ld-* $PREFIX/lib
cp -d /lib/libnsl* $PREFIX/lib
cp -R -d /usr/lib/gconv $PREFIX/usr/lib
cp -R $PHP_PREFIX/lib/php $PREFIX/$PHP_PREFIX/lib
cp -d -R /usr/lib/locale/en_GB $PREFIX/usr/lib/locale
cp -d -R /usr/lib/locale/en_GB.utf8 $PREFIX/usr/lib/locale
cp -d -R /usr/lib/locale/ru_RU $PREFIX/usr/lib/locale
cp -d -R /usr/lib/locale/ru_RU.koi8r $PREFIX/usr/lib/locale
cp -d -R /usr/lib/locale/ru_RU.utf8 $PREFIX/usr/lib/locale

      Обратите внимание на несколько моментов:

  • из /etc/passwd мы взяли только записи про apache, nobody, nogroup (не стоит подвергать информацию о других учетных записях опасности быть украденой при взломе этого конкретного сервиса, исключение составляет только ситуация с использованием ~/public_html/)
  • не забывайте про /etc/localtime, если хотите, чтобы сервис жил с вами в одной временной зоне
  • по-минимуму можно было не копировать все бинарные файлы Apache, а обойтись только httpd
  • обвязка ldd в этом скрипте сделана на скорую руку (однако работает в подавляющем большинстве случаев) - имя какой-нибудь библиотеки, нестандартно выведенной, может не захватиться в переменную
  • не забывайте про /etc/nsswitch.conf и resolv.conf при помещении в CE сетевых сервисов
  • также не забывайте про библиотеки libnss, libnss_dns и libnsl
  • при желании уменьшить объем, занимаемый библиотеками и исполняемыми бинарными файлами - после копирования и тестирования работоспособности CE сделайте им strip
  • файлы из /usr/lib/gconv и /usr/lib/locale нужны для корректной работы с локалями, отличными от английской (C) - без этих файлов, например, русские буквы не будут восприниматься как корректные символы алфавита, что может сказаться на работе регулярных выражений в PHP
  • некоторым сервисам может требоваться не только файл устройства /dev/null, но и /dev/random вместе с /dev/urandom (например, для того же BIND - иначе у него не работают корректно функции шифрования)
  • если вам для работы каких-нибудь CGI-скриптов необходим PERL, его так же необходимо поместить в CE, не забыв про /usr/lib/perl5
  • учтите, что если вы не предпримете никаких дополнительных мер, PHP функция mail() не будет работать в CE, т.к. она пытается вызвать бинарный файл SMTP-клиента, который отсутствует в CE (в интернете описаны методы обхода этого с использованием модулей из репозитария PEAR)
  • не пренебрегайте возможностью установить (с помощью утилиты chattr) флаг иммунитета к изменениям (immutable) для важных файлов, которые не должны меняться в ходе работы - например, для конфигурационных файлов
Возможно, есть еще какие-то тонкости, однако я в своей практике с ними не сталкивался, и поэтому написать ничего про них не могу. Помещение MySQL в CE производится аналогично, никаких особых подводных камней там нет, помимо уже описанных.

Проблемы и пути решения

Взаимодействие с другими сервисами

      Итак, допустим, что мы благополучно упрятали сервис в CE. Казалось бы - дело сделано, можно идти пить согревающие напитки с друзьями, но в душу закрадываются какие-то смутные подозрения о том, что это не конец. И они вовсе не беспочвенны. Типовой пример - те же самые Apache + PHP + MySQL.

      Предположим, что Apache с PHP у нас загорают в "тюрьме" CE, а MySQL "остался на свободе". Но им же надо как-то взаимодействовать друг с другом! Поместить MySQL в тот же CE - не очень хорошее решение (лучше его отсадить в отдельный CE, исходя из народной мудрости, учащей раскладывать яйца по разным корзинкам). С другой стороны, нарушать целостность CE тоже не хочется - иначе какой тогда в нем смысл? В такой ситуации есть неплохое решение: настроить связь с MySQL через UNIX domain socket (по умолчанию они так и взаимодействуют) и сделать на него жесткую ссылку (hard link) в CE с Apache. Единственное ограничение - жесткие ссылки работают только внутри одного раздела диска, поэтому лучше хранить все CE на одном разделе. В этой связи возникает новая проблема - слежение за тем, чтобы этот раздел не переполнился в результате действий пользователей или раздувания логов и тем самым не заблокировалась работа сервера (если речь идет о корневом разделе основной системы). Одним из эффективных путей предотвращения таких ситуаций может стать использование файловых квот, однако это уже выходит за рамки данной статьи. Помимо этого, не забывайте о том, что файл существует до тех пор, пока существует хотя бы одна жесткая ссылка на него - поэтому рекомендую в выше рассмотренном примере с MySQL убивать при остановке MySQL все жесткие ссылки на mysql.sock, а при запуске - создавать заново.

      Альтернативным подходом является использование TCP сокетов для межпроцессного взаимодействия. Выбор конкретного решения - дело каждого.

Журналирование

      Аналогичным же образом поступаем и с журналированием событий через syslog - настраиваем его для работы через дополнительный сокет (опция -a для syslogd) или пробрасываем жесткую ссылку с /dev/log в CE. Тем самым мы получаем обычное журналирование согласно syslog.conf, как будто бы сервис и не находился в CE.

Доступ до части основной файловой системы

      Полная изоляция - это хорошо, но не всегда то, что надо. Пример из жизни: у пользователей на сервере есть свои веб-страницы, лежащие в ~/public_html, и адресуемые http://site/~user/. Как быть с ними, если веб-сервер в CE? Копировать пользовательские файлы - абсурд. Снова воспользоваться жесткими ссылками будет чрезвычайно неудобно при большом количестве файлов и, ко всему же, жесткие ссылки не работают для каталогов. Есть идея лучше: воспользоваться возможностью монтировать каталог в каталог (mount с опцией --bind). Т.е. мы просто при старте веб-сервера монтируем /home в /chroot/httpd/home, например, а при остановке - размонтируем. Однако здесь есть крупный подводный камень, из-за которого я и рекомендую все время размонтировать такие каталоги после остановки сервиса - на этапе настройки вы наверняка не с первого раза создадите работающее CE, и, возможно, будете несколько раз стирать /chroot/service - и не дай Бог в этот момент не отмонтировать оттуда каталоги - вы уже наверняка догадались, что произойдет в этом случае - нужные файлы отправятся прямиком в /dev/null.

Уязвимости CE

      Как это ни прискорбно, наш мир далек от идеала, и программное обеспечение здесь не является исключением из правил. Из CE можно "выпрыгнуть", причем, иногда очень легко. Один из методов "побега" описан прямо в документации по chroot - цитирую: In particular, the super-user can escape from a `chroot jail' by doing `mkdir foo; chroot foo; cd ..'.
Чтобы не делать из этого материала руководства по побегу из chroot, не буду развивать эту тему - она достаточно хорошо освещена на соотвествующих тематических сайтах.

      Итак, существует ряд проблем с безопасной работой в CE, однако существуют и контрмеры по их устранению. Одна из наиболее эффективных подпорок CE - это набор патчей на ядро под названием grsecurity. Эти патчи достойны настоящего параноика и позволяют достаточно туго затянуть гайки (вплоть до доведения системы до самого защищенного состояния - неработающего). Из интересующего нас в аспекте усиления CE можно отметить раздел 'Chroot jail restrictions', в котором:

  • запрет mount
  • запрет двойных вызовов chroot()
  • запрет использования pivot_root() (аналога chroot())
  • принудительная смена текущего каталога на "/"
  • запрет установки suid и sgid флагов на файлы
  • запрет fchdir() (иначе из CE можно сбежать через открытый файловый дескриптор, ведущий наружу)
  • запрет создания файлов устройств
  • запрет использования shared memory за границами CE
  • запрет подключения к абстрактным Unix domain sockets
  • запрет на управление процессами вне CE (чтобы из CE нельзя было убить внешний процесс либо еще как-то на него повлиять)
  • запрет смены приоритета процессов вне CE (иначе возможна реализация DoS при выставлении минимального приоритета на другие работающие сервисы)
  • запрет манипулирования sysctl записями
  • запрет подключения модулей ядра, raw I/O операций, перезагрузки системы, смены системного времени, изменения файлов с иммунитетом и многое другое
Если вы подумаете: "а зачем такой концлагерь?" - я рискуя, окончательно разрушить вашу веру в устойчивость программного обеспечения, скажу, что почти каждый из пунктов перечисленного списка позволяет "сбежать" из CE. А вообще grsecurity содержит много интересного, рекомендую принять на вооружение. Единственный неприятный момент, связанный с этим набором патчей - некоторая инертность разработчика (порой выпуски свежих версий патчей задерживаются).

      При упоминании grsecurity на ум сразу приходят патчи проекта Openwall (который уже вырос в дистрибутив Openwall GNU/*/Linux, ориентированный на усиление безопасности) - однако, они менее эффективны и не предоставляют возможности тонкой настройки параметров.

Автоматизация

      Еще раз хотелось бы заострить внимание на автоматизации процесса: сисадмины, пишите скрипты! Не забывайте про китайский принцип "три года упорного труда - десять тысяч лет счастья" (c) Мао Цзе-дун.

      Если серьезно, то на каждый сервис вам понадобится 3 скрипта - для начального развертывания CE, для его аккуратного апгрейда без убийства хранящихся там данных и конфигов, и скрипт запуска/остановки. Так вы сможете поддерживать сервис в актуальном состоянии - обновляете в основной системе пакет с искомым сервисом, запускаете скрипт апгрейда выбранного CE и остается только заново его запустить.

      В качестве конкретного примера приведу пример стартового скрипта для Apache в CE (продолжая начатую тему веб-сервера в CE):

#!/bin/sh
# start/stop/restart chrooted Apache web server
# Den aka Diesel <diesel@sherdart.net>

PREFIX=/chroot/httpd
APACHE_PREFIX=/usr
MYSQL_SOCK=/var/run/mysql/mysql.sock

# если вы хотите использовать каталоги ~/public_html в CE, установите
# HOMES_BIND=1
# и постоянно синхронизируйте $PREFIX/etc/passwd с основной системой

HOMES_BIND=0

httpd_start() {
	if [ -x $PREFIX/$APACHE_PREFIX/sbin/httpd ]; then
		echo "Starting Apache web server in the chroot environment..."
		
		if [ HOMES_BIND = 1 ]; then
			mount --bind /home $PREFIX/home
		fi
		if [ -e $MYSQL_SOCK ]; then
			ln $MYSQL_SOCK $PREFIX$MYSQL_SOCK
		fi
		if ! `/usr/sbin/chroot $PREFIX $APACHE_PREFIX/sbin/httpd` ; then
			echo "ERROR"
			httpd_stop
		fi
	fi
}

httpd_stop() {
	killall httpd
	if [ HOMES_BIND = 1 ]; then
		umount $PREFIX/home
	fi
	if [ -e $PREFIX/$MYSQL_SOCK ]; then
		rm -f $PREFIX/$MYSQL_SOCK
	fi
}

httpd_restart() {
	httpd_stop
	sleep 1
	httpd_start
}

case "$1" in
'start')
	httpd_start
	;;
'stop')
	httpd_stop
	;;
'restart')
	httpd_restart
	;;
*)
	echo "usage $0 start|stop|restart"
esac

Альтернативы

      В качестве альтернатив помещению сервисов в CE мне видится только разнос сервисов по разным физическим машинам или виртуализация, т.е. организация на одном физическом сервере нескольких виртуальных посредством серверных решений компаний VMware, SWsoft и аналогов. В Linux существует хорошее средство виртуализации - vserver, позволяющее размещать на одном компьютере несколько систем, объединенных лишь общим ядром. Правда, оба эти метода связаны с большими накладными расходами, поэтому в ряде случаев более оправдано использовать chrooted environment, если нет возможности нарастить оборудование.

В качестве заключения

      "Кто осведомлен - тот вооружен" - гласит древняя мудрость, поэтому подпишитесь на пару толковых списков рассылки по безопасности, вовремя устанавливайте обновления, берегите лог-файлы (еще лучше - складируйте их на другой машине), все время делайте резервные копии важной информации (лучше - две копии), переходите улицу только на зеленый свет и вообще меняйте работу на менее нервную ;)

      PS: автор благодарит TLUG (Tomsk Linux Users Group) за конструктивные замечания, высказанные при подготовке статьи.

Статья написана по материалам доклада на семинаре, посвященном применению Linux и open source ПО, прошедшем 17.09.05 в г. Томске.

27.12.2005
Den aka Diesel

Каталог BigMax.ru