FreeBSD хороша в качестве серверной ОС – поломать ее непросто даже в базовой
конфигурации. Но не всякое ПО может похвастаться такой же надежностью и
оперативностью исправления ошибок. Поэтому на важных серверах принято
использовать jail, который запирает небезопасное стороннее ПО на замок, не
позволяя взломщику навредить операционной системе.
От песочницы до тюрьмы – один шаг
По поручению руководства ты поднимаешь корпоративный ftp-сервер, долго
настраиваешь права доступа, наполняешь контентом, заботливо окружаешь файрволом
и с чувством выполненного долга отправляешься домой, а наутро обнаруживаешь на
главной страничке корпоративного портала злобную надпись "You are hacked!". "Что
за бред", - думаешь ты, пытаясь обнаружить следы и способ проникновения, которым
оказывается… естественно, уязвимый ftp-сервер. Директор как всегда зажал денег
на выделенную машину для ftp-сервера, и поэтому приходится латать дыры и
надеяться, что в следующий раз ты вовремя накатишь важные обновления.
Это – несколько надуманная провинциальная история, но она хорошо отражает
суть проблемы. Система оказывается беззащитной перед взломщиком:
скомпрометировав один из сервисов, злоумышленник автоматически получает полный
доступ ко всем остальным. И даже если служба работает с правами
непривилегированного пользователя (что подчас невозможно), грамотный специалист
сможет использовать имеющиеся права для исследования системы, повышения прав
через локальные уязвимости или установки различных бэкдоров.
Невольно напрашивается мысль об отделении опасного сервиса от ОС, его
изоляции, помещении в безопасную среду, из которой он не сможет навредить
основной системе. Такой изолятор называется "песочницей" и реализуется с помощью
системного вызова chroot,
который заставляет приложение думать, что указанный в аргументе каталог - это
корень файловой системы. Как результат, запущенная программа работает в каталоге
/usr/chroot (для примера) и не может навредить основной системе (ведь подняться
на каталог выше корня нельзя). Песочница хорошо подходит для сервисов,
работающих с правами рядовых пользователей, но, как только программа получает
привилегии root, все рушится. Суперпользователь остается при своих правах и в
chroot, поэтому может загружать модули ядра, монтировать файловые системы и
делать все, что душа пожелает. Поэтому для "особо опасных" изолятор уже не
подходит, – нужна настоящая тюрьма!
За решеткой
Технология jail базируется на системном вызове chroot, но отличается тем, что
существенно ограничивает суперпользователя в правах. Находясь "за решеткой",
root не имеет прав:
- Загружать модули ядра и каким-либо образом модифицировать ядро
(например, через /dev/kmem).
- Изменять переменные ядра (за исключением kern.securelevel и
kern.hostname).
- Создавать файлы устройств.
- Монтировать и демонтировать файловые системы.
- Изменять сетевые конфигурации.
- Создавать raw сокеты (поведение настраивается).
- Получать доступ к сетевым ресурсам, не ассоциированным с IP-адресом
jail'а.
- Работать с System V IPC (поведение настраивается).
- Присоединяться к процессу и использовать ptrace(2).
В jail суперпользователь превращается в капризного ребенка, который, хоть и
не может навредить корневой системе, без особых проблем введет ОС в ступор
полной нагрузкой на процессор или сожрет всю доступную память. К сожалению,
против подобного вандализма защиты пока нет. Как нет и против тех, кто захочет
использовать изолированный сервис в корыстных целях, будь то рассылка спама или
хранилище вареза. Тюрьма отлично решает проблему проникновения в основную
систему, но сама по себе абсолютно беззащитна.
Перед тем, как поместить выбранный сервис в jail, мы должны создать для него
все необходимые условия, эдакую швейцарскую тюрьму, где комфортно, как дома. Для
этого внутрь выбранного каталога необходимо поместить минимальную копию корневой
системы, – чтобы сервис смог найти необходимые ему каталоги, библиотеки и
конфигурационные файлы (окружение исполнения). Самый простой путь - просто
скопировать все необходимое из существующей системы, но он чреват ошибками, и
есть риск получить "грязное" окружение, которое отразит некоторые аспекты
реальной системы и поможет взломщику. Поэтому лучше собрать "чистое" окружение
из исходных текстов и установить в выбранный каталог. Так сервис получит среду в
дефолтовой конфигурации, ничего не говорящей о состоянии реальной системы.
Шаг 1. Создание jail-окружения
Переходим в каталог /usr/src и выполняем следующую последовательность команд:
# JAIL=/usr/jail/base
# mkdir -p $JAIL
# make world DESTDIR=$JAIL
# make distribution DESTDIR=$JAIL
# mount -t devfs devfs $JAIL/dev
После их исполнения каталог /usr/jail/base будет содержать девственно чистое
базовое окружение FreeBSD, включая виртуальную файловую систему /dev.
Шаг 2. Настройка корневой системы
Идем дальше. Jail-окружения во FreeBSD реализованы через привязку к
IP-псевдонимам сетевых интерфейсов, поэтому, во-первых, мы должны назначить
алиас интерфейсу, смотрящему "наружу", а во-вторых, сделать так, чтобы сервисы
корневой системы слушали порты только на своем IP и "пропускали мимо ушей"
трафик, предназначенный сервису в jail.
IP-псевдонимы для сетевых интерфейсов назначаются с помощью команды:
# ifconfig ed0 inet alias 192.168.0.1/16
Чтобы команда исполнялась во время загрузки, добавляем ее в /etc/rc.conf:
# echo "ifconfig_ed0_alias0=\"inet 192.168.0.1\"" >> /etc/rc.conf
Ничего страшного, если в твоем распоряжении нет второго глобально
маршрутизируемого IP-адреса, – подойдет любой адрес из частного диапазона
(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), трафик на который мы завернем с
помощью брандмауэра.
Теперь о настройке сервисов корневой системы. Если ты все-таки решил
привязать jail ко второму внешнему IP-адресу, создав как бы иллюзию второго
сервера в сети, тебе придется настроить все сервисы корневой системы на
прослушивание только первого IP-адреса. Иначе можно поймать конфликты между
приложениями, слушающими один порт (например, ssh внутри тюрьмы и в корневой
системе). В большинстве случаев сделать это можно через редактирование
конфигурационного файла или путем указания специальных флагов:
# echo "inetd_flags=\"-wW -a <IP-адрес корневой машины>\"" >> /etc/rc.conf
К несчастью, некоторые сервисы (rpcbind, nfsd, mountd) не позволяют указать
прослушиваемый ими IP-адрес, поэтому постарайся не запускать их в базовой
системе и jail-окружении одновременно. С локальным IP-адресом этого делать не
придется, так как jail-сервисы извне доступны не будут. Зато придется
позаботиться об организации форвардинга с помощью брандмауэра (пример для ssh):
# ipfw add fwd 192.168.0.1,22 tcp from any to внешний-ip 22
Шаг 3. Настройка jail-окружения
Работающее jail-окружение представляет собой почти точную копию настоящей
FreeBSD и даже загружается через запуск стартового скрипта /etc/rc. В то же
время внутри jail действуют свои ограничения и особые правила, которые
необходимо учитывать. Поэтому входим в тюрьму:
# jail /usr/jail/base base.jail 192.168.0.1 /bin/sh
И выполняем следующую последовательность действий:
- Создаем пустой файл fstab (touch /etc/fstab), чтобы скрипты
инициализации не ругались на его отсутствие.
- Устанавливаем пароль для суперпользователя (passwd root) и создаем, если
необходимо, дополнительных пользователей.
- Перестраиваем базу почтовых псевдонимов (newaliases), если хотим
использовать sendmail.
- Настраиваем временную зону (tzsetup).
- Редактируем /etc/resolv.conf таким образом, чтобы сервисы, запущенные
внутри jail'а, могли выполнять DNS-резолвинг. Можно указать адрес
хост-системы, если в ней запущен кэширующий DNS-сервер.
- Добавляем в /etc/rc.conf следующие строки:
# vi /etc/rc.conf
// Сетевое имя jail-окружения
hostname="base.jail"
// Отключаем конфигурирование сетевых интерфейсов (они виртуальные)
network_interfaces=""
// Включаем/отключаем необходимые сервисы
sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
sshd_enable="YES"
После того, как все будет сделано, выходим из окружения, набрав exit или
нажав <Ctrl+D>.
В случае использования локального IP-адреса для тюрьмы, его сетевое имя может
быть любым. Но если jail привязан к внешнему IP, потребуется, конечно же,
выбрать настоящее доменное имя, прописанное в DNS-зонах.
Пример иллюстрирует запуск ssh-сервера внутри jail-окружения, но что, если
нужного сервиса нет в базовом дистрибутиве? Проще всего - указать путь установки
через переменную PREFIX во время установки порта:
# make PREFIX=/usr/jail/base make install clean
Для пакетов:
# pkg_add -P /usr/jail/base пакет-1.0.0.tbz
Для пользователей portinstall:
# PREFIX=/usr/jail/base portinstall -P пакет
К сожалению, в некоторых случаях прямое указание пути установки не подходит.
Например, ты можешь поднять множество jail-серверов на быстрой машине и
предлагать людям услуги по предоставлению в аренду выделенного сервера FreeBSD,
с которым они смогут делать все, что захотят. И если ты не позволишь клиентам
самостоятельно устанавливать стороннее ПО через порты, их поток вскоре иссякнет.
Простой способ избежать этого - скопировать порты из базовой системы в каждое из
jail-окружений. Но это очень грубый подход, расходующий дисковое пространство и
отнимающий время на синхронизацию с новым деревом портов. Гораздо проще и
разумнее применить виртуальные файловые системы вроде unionfs и nullfs для
монтирования каталога /usr/ports ко всем тюрьмам:
# mount_unionfs /usr/ports /usr/jail/base/usr/ports
или
# mount_nullfs /usr/ports /usr/jail/base/usr/ports
Шаг 4. Запуск jail-окружения
Все подготовительные работы выполнены, осталось только запустить готовый
виртуальный jail-сервер. Для этого добавляем в /etc/rc.conf следующие строки:
# vi /etc/rc.conf
jail_enable="YES"
// Список jail-окружений
jail_list="base"
// Стандартные опции jail
jail_base_rootdir="/usr/jail/base"
jail_base_hostname="base.jail"
jail_base_ip="192.168.0.1"
jail_base_interface="de0"
// Команды запуска и остановки
jail_base_exec_start="/bin/sh /etc/rc"
jail_base_exec_stop="/bin/sh /etc/rc.shutdown"
// Какие ФС монтировать?
jail_base_devfs_enable="YES"
jail_base_fdescfs_enable="NO"
jail_base_procfs_enable="NO"
И запускаем:
# /etc/rc.d/jail start base
Список запущенных jail-окружений всегда доступен по команде /usr/sbin/jls.
Процессы, заключенные в тюрьму, отображаются в выводах ps и top. Их
отличительная черта - флаг 'J'.
Как это делают джедаи
Выше был описан "официальный" способ создания jail-окружений, подходящий
почти для всех случаев. Его достоинства в универсальности и относительной
простоте развертывания виртуального сервера. С другой стороны, для единичного
сервиса, помещенного в тюрьму, полное окружение исполнения - явное излишество,
съедающее свободное пространство и несущее угрозу безопасности. Взломщик,
проникший в тюрьму, получит в распоряжение цельную операционную систему с
командным интерпретатором, компилятором, ssh-сервером и массой других подсобных
утилит. Стоит ли говорить, чем грозит такая свобода выбора?
Главное правило, которым следует руководствоваться при создании разного рода
песочниц, тюрем и виртуальных серверов – "чем проще, тем лучше". По возможности
из окружения следует убрать все, что не влияет на работу сервиса, включая
библиотеки, конфигурационные файлы и, в особенности, различные подсобные утилиты
и консольные команды вроде ls, cd и sh. Отдельному сервису не нужна швейцарская
тюрьма с удобствами, ему больше подойдет русская зона.
Для иллюстрации того, как все это проделать в реальных условиях, рассмотрим
процесс помещения nginx, работающего в режиме reverse-proxy, внутрь jail.
Представим, что у нас есть apache, работающий в корневой системе и слушающий
порт 8080. Задача: поместить перед ним nginx, слушающий 80-й порт и
перенаправляющий запросы к apache. Чтобы обезопасить apache, мы решаем поместить
nginx в jail. Как это сделать?
Для начала создадим для нашей тюрьмы каталог и установим туда nginx (здесь и
далее приведены команды для bash):
# JAIL=/usr/jail/nginx
# mkdir -p $JAIL
# cd /usr/ports/www/nginx
# make PREFIX=$JAIL install clean
Выясним, какие библиотеки нужны nginx для работы:
# ldd $JAIL/sbin/nginx
И скопируем их в каталог /usr/jail/nginx/lib:
# mkdir -p $JAIL/lib
# LIBS=`ldd $JAIL/sbin/nginx|grep -v ':$'|cut -f 3 -d " "`
# for LIB in $LIBS; do cp $LIB $JAIL/lib; done
Кроме того, необходимо скопировать и настроить линковщик ld-elf.so.1, без
него не запустится ни один исполняемый файл:
# mkdir -p $JAIL/libexec
# cp /libexec/ld-elf.so.1 $JAIL/libexec
# mkdir -p $JAIL/var/run
# ldconfig -s -f $JAIL/var/run/ld-elf.so.hints $JAIL/lib
Заведем пользователя и группу www:
# echo 'www:*:80:80::0:0:World Wide Web Owner:/nonexistent:/usr/sbin/nologin'
> $JAIL/etc/passwd
# cp $JAIL/etc/{passwd,master.passwd}
# pwd_mkdb -d $JAIL/etc $JAIL/etc/master.passwd
# echo 'www:*:80:' > $JAIL/etc/group
Создадим каталоги, необходимые для нормальной работы сервиса:
# mkdir -p $JAIL/var/{log,tmp/nginx}
# chown 80:80 $JAIL/var/tmp/nginx
# mkdir $JAIL/{dev,tmp}
# chmod 7777 $JAIL/tmp
Смонтируем файловую систему devfs:
# mount -t devfs devfs $JAIL/dev
Создадим IP-псевдоним и настроим брандмауэр на редирект HTTP-трафика на
IP-адрес тюрьмы:
# ifconfig ed0 inet alias 192.168.0.1/16
# ipfw add fwd 192.168.0.1,80 tcp from any to внешний-ip 80
Откроем конфигурационный файл nginx и приведем секцию server к следующему
виду:
# vi /usr/jail/nginx/etc/nginx/nginx.conf
server {
listen 80;
server_name www.host.ru;
location / {
proxy_pass http://127.0.0.1:8080/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}
}
Учти, что хитрая система портов изменила дефолтовые пути поиска файлов в
nginx, добавив к ним префикс /usr/jail/nginx. Поэтому все относительные пути в
файле конфигурации придется заменить на абсолютные, то есть – прописать вместо "include
mime.types;" строку "include /etc/nginx/mime.types;".
Все, теперь виртуальный сервер с nginx можно запустить (опция '-c' позволяет
переписать неверный дефолтовый путь поиска конфигурационного файла):
# jail /usr/jail/nginx nginx.jail 192.168.0.1 /sbin/nginx -c /etc/nginx/nginx.conf
Чтобы nginx стартовал при загрузке, добавим в /etc/rc.conf следующие записи:
# vi /etc/rc.conf
ifconfig_ed0_alias0="inet 192.168.0.1"
jail_enable="YES"
jail_list="nginx"
jail_nginx_rootdir="/usr/jail/nginx"
jail_nginx_hostname="nginx.jail"
jail_nginx_ip="192.168.0.1"
// Полная инициализация окружения не нужна, достаточно сразу запустить сервис
jail_nginx_exec_start="/sbin/nginx -c /etc/nginx/nginx.conf"
// Останавливать nginx вручную также не требуется, перед завершением работы jail
аккуратно убьет все свои процессы с помощью kill
jail_nginx_exec_stop=""
// Нам понадобится только devfs
jail_nginx_devfs_enable="YES"
jail_nginx_fdescfs_enable="NO"
jail_nginx_procfs_enable="NO"
По описанной схеме в тюрьму можно посадить практически любой сервис, не
отягощенный множеством зависимостей. В некоторых случаях придется повозиться с
созданием файлов и каталогов, а также с отслеживанием необходимых библиотек
(некоторые сетевые серверы, например sshd, загружают библиотеки во время
исполнения, так что ldd покажет не все, и придется воспользоваться lsof).
Проблему также представляет /dev. Весьма опрометчиво открывать все файлы этого
каталога на чтение, а уж тем более, на запись, – поэтому для регулирования прав
доступа необходимо использовать специальные настройки devfs.
Файл /etc/defaults/devfs.rules содержит базовые правила devfs для jail. По
умолчанию он открывает доступ к подсобным синтетическим файлам, таким как /dev/null
и /dev/random, а также псевдотерминалам. Для большинства конфигураций настройки
даже не придется редактировать, достаточно скопировать файл в каталог /etc и
добавить в /etc/rc.conf следующую запись:
jail_имя_devfs_ruleset="devfsrules_jail"
Если же понадобятся дополнительные файлы устройств, то devfs.rules легко
отредактировать, добавив необходимые правила. Синтаксис файла и правил описаны
на man-страницах devfs и devfs.rules.
Переменные sysctl, о которых нужно знать
security.jail.set_hostname_allowed - может ли jail-суперпользователь
изменять сетевое имя (hostname) jail-сервера. Имеет смысл отключить, если для
тюрьмы выделено настоящее сетевое имя, прописанное в DNS-зонах.
security.jail.allow_raw_sockets - разрешить jail-суперпользователю
создавать raw-сокеты. В целях безопасности опция отключена, но она мешает
правильной работе некоторых инструментов, предназначенных для отладки сети.
security.jail.chflags_allowed - позволить jail-процессам
модифицировать флаги ФС (chflags). По умолчанию выключена, что открывает
интересные возможности для помещения в jail неудаляемых, нечитаемых или
незаписываемых файлов.
|