Василий Озеров
Как увеличить безопасность процессов с помощью chroot и jail
Chroot и Jail – два разных способа изолировать процесс от основной файловой системы. Где и когда их использовать? Каковы их преимущества и недостатки?
Большинство сервисов, работающих в системе, имеют возможность просматривать структуру всей файловой системы сервера. Это не всегда безопасно для информации, хранящейся на сервере. Для того чтобы не допустить кражу секретной информации, были придуманы способы, позволяющие изолировать процесс: chroot и jail.
Chroot
Chroot – операция изменения корневого каталога для текущего процесса и всех его потомков – поможет увеличить безопасность системы и протестировать новое ПО.
При увеличении безопасности процесса принципиальное значение имеют свойства процесса, а именно: с какими привилегиями он работает. Если процесс был запущен от имени обычного пользователя, то все проблемы с безопасностью решаются грамотной установкой прав на файлы и каталоги. Процесс, запущенный от имени суперпользователя, имеет абсолютные привилегии в системе, благодаря которым может выполнять любые действия. Для того чтобы снизить возможный ущерб от взломанного сервиса, следует изолировать его от основной системы, используя chroot. Тогда сервис будет иметь доступ только к той части файловой системы, в которой он «заперт».
Используя chroot для тестирования ПО, вы можете не бояться за основную систему, ведь процесс не может получить к ней доступ, не говоря уже об удалении важных файлов.
Для того чтобы перенести программу с одной системы на другую, не имея исходных кодов, достаточно перенести все библиотеки, нужные для работы программы, и запустить саму программу в chroot-окружении. Думаю, преимущества chroot очевидны.
Отмечу недостатки chroot:
- Увеличивается число копий одних и тех же файлов.
- Возможность выйти из chroot-окружения (на некоторых ядрах).
Первый случай я поясню на примере: мы пытаемся запустить в «песочнице» /bin/sh. Корнем этого процесса сделаем /usr/chroot/. Посмотрим, от каких библиотек зависит sh:
# ldd /bin/sh
/bin/sh:
libedit.so.5 => /lib/libedit.so.5 (0x2808c000)
libncurses.so.6 => /lib/libncurses.so.6 (0x280a0000)
libc.so.6 => /lib/libc.so.6 (0x280df000)
|
Так как для sh теперь корнем будет /usr/chroot, то библиотеки должны быть расположены следующим образом (относительно реального корня): /usr/chroot/lib/*. Обратите внимание, что это не все библиотеки, которые необходимо перенести. Очевидно, что нерационально расходуется место на жестком диске. Если же попытаться запустить связку Apache + MySQL + PHP в chroot, то придется переносить чуть ли не больше половины всей системы. А теперь задайте себе вопрос: а действительно ли переносить всю связку так необходимо? Ведь Apache работает не от имени суперпользователя, и главное – правильно выставить права на файлы и каталоги.
О возможности выйти из chroot я расскажу чуть позже.
Вернемся к нашей теме «Как увеличить безопасность системы с помощью chroot». Я написал небольшую программку на C, которая наглядно демонстрирует работу chroot:
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
int main(void)
{
char line[255]=""; /* Переменная для названия файла */
chdir("/"); /* Перемещаемся в корень */
printf("Enter filename to create: ");
scanf("%s", &line);
if((fopen(line,"w+") == NULL)) /* Создаем файл */
{
perror("Error"); /* Выводим сообщение об ошибке */
exit(1); /* И выходим, если создать файл не удалось */
}
return 0;
}
Как вы видите из кода, первым делом мы перемещаемся в кореневой каталог файловой системы, а потом уже там создаем файл. Теперь посмотрим, как будет работать программа в chroot-кружении и без него.
Компилируем:
# gcc prog.c -o prog
# ls -la prog
-rwxr-xr-x 1 root wheel 5533 Jun 2 00:52 prog |
После этого запустим программу без chroot-окружения и создадим, например, файл chr00t.
Проверяем наличие файла «/chr00t», как видно, такого файла не существует:
# ls -la /chr00t
ls: /chr00t: No such file or directory |
Запускаем программу:
# ./prog
Enter filename to create: chr00t |
Опять проверяем наличие файла «/chr00t», как видно, файл успешно был создан:
# ls -la /chr00t
-rw-r--r-- 1 root wheel 0 Jun 2 00:54 /chr00t |
Очистим рабочее пространство для будущих экспериментов:
# rm -f /chr00t
Теперь запустим нашу программу в chroot-среде, но для этого предварительно необходимо создать нужное нам дерево каталогов и перенести туда соответствующие файлы.
Создаем дерево каталогов. В частности, папки lib и libexec для нужных библиотек:
# mkdir -p /usr/chroot/{lib,libexec}
Переносим нашу программу в /usr/chroot:
# cp ./prog /usr/chroot/
Смотрим, с чем связана наша программа:
# ldd /usr/chroot/prog
/usr/chroot/prog:
libc.so.6 => /lib/libc.so.6 (0x28074000)
|
Копируем необходимые библиотеки:
# cp /lib/libc.so.6 /usr/chroot/lib/
Библиотека ld-elf.so.1 нужна для запуска бинарных файлов формата elf:
# cp /libexec/ld-elf.so.1 /usr/chroot/libexec/
После того как все предварительные шаги сделаны, можно запускать нашу программу в chroot-окружение, но перед этим проверим наличие файла в /usr/chroot и в /.
Следующие две команды проверяют наличие файла в /usr/chroot и в /:
# ls -la /chr00t
ls: /chr00t: No such file or directory |
# ls -la /usr/chroot/chr00t
ls: /usr/chroot/chr00t: No such file or directory |
Запускаем нашу программу в изолированной среде:
# chroot /usr/chroot/ /prog
Enter filename to create: chr00t |
Проверяем наличие файла в корневом каталоге процесса:
# ls -al /usr/chroot/chr00t
-rw-r--r-- 1 root wheel 0 Jun 2 00:56 /usr/chroot/chr00t |
# ls -la /chr00t
ls: /chr00t: No such file or directory |
Как видите, файл успешно создался не в настоящем корне системы, а в корневом каталоге для данного процесса. На этом примере прекрасно видно, что процесс, «посаженный в песочницу», не имеет доступа ко всей файловой системе, а только к /usr/chroot/.
Выход из chroot-окружения
Как я уже писал ранее, возможен обход chroot-окружения, но для этого одновременно должны выполняться два условия:
- Процесс должен работать под правами суперпользователя.
- Ядро должно поддерживать вложенные chroot-вызовы.
Запускаем нашу программу под правами суперпользователя, поэтому одно из условий уже соблюдается.
Со вторым условием потруднее. Я протестировал свою программу для выхода из chroot на следующих системах:
- FreeBSD 6.0/6.1
- FreeBSD 5.4
- FreeBSD 4.6
И только четвертая версия ОС FreeBSD выпустила меня за пределы chroot, а следовательно, из chroot в системах FreeBSD 5.x/6.x выйти невозможно, по крайней мере мне не удалось.
Но я все же расскажу о выходе из «песочницы». Для этого процессу необходимо выполнить следующие действия:
- Создать временную(tmp) директорию в текущем рабочем каталоге.
- Открыть временную директорию (нужно, только если вызов chroot меняет текущую рабочую директорию).
- Изменить корневой каталог на временную директорию вызовом chroot.
- Использую fchdir выйти из chroot-окружения (нужно, только если вызов chroot меняет текущую рабочую директорию).
- Много раз выполнить chdir(“..”) для перемещения в реальный корень файловой системы.
- Изменить корневой каталог процесса на текущий (chroot(“.”)), то есть реальный корень.
Вот как этот алгоритм выглядит на C (этот код нужно вставить после строки chdir(“/”) в предыдущей программе):
mkdir("tmp", 0755); /* Создаем временную директорию */
dir_fd = open(".", O_RDONLY); /* Открываем текущую директорию */
chroot("tmp"); /* Меняем корневую директорию для процесса*/
fchdir(dir_fd); /* Частично выходим из chroot, поднимаясь на уровень вверх */
close(dir_fd); /* Закрываем дескриптор директории */
for(n = 0; n < 1024; n++)
{
chdir(".."); /* Поднимаемся на уровень выше */
}
chroot("."); /* Меняем корневой каталог процесса на настоящий */
Код достаточно прокомментирован, поэтому разобраться в нем не составит труда.
Итак, запустим новую программу на FreeBSD 4.6.
Проверяем наличие файла «/chr00t», такого файла нет:
# ls -la /chr00t
ls: /chr00t: No such file or directory |
Создаем папки для библиотек:
# mkdir -p /usr/chroot/usr/{lib,libexec}
Компилируем программу:
# gcc break_chroot.c -o /usr/chroot/break_chroot
Копируем необходимые библиотеки:
# cp /usr/lib/libc.so.4 /usr/chroot/usr/lib/
# cp /usr/libexec/ld-elf.so.1 /usr/chroot/usr/libexec/
Запускаем программу в chroot-среде:
# chroot /usr/chroot/ /break_chroot
Enter filename to create: chr00t |
Проверяем наличие файла «/chr00t»:
# ls -la /chr00t
-rw-r--r-- 1 root wheel 0 Jun 2 00:56 /chr00t |
Как хорошо видно, файл был создан не в chroot-окружении, а в настоящем корневом каталоге файловой системы. Чтобы не дать выйти программе за пределы chroot-окружения, следует использовать функции setuid, setgid для изменения EUID, EGID-процесса. Так как вызов chroot может быть вызван только суперпользователем, то программа просто не сможет сделать еще один chroot.
Тестирование ПО с помощью chroot
Если нужно протестировать какую-нибудь программу, а под рукой нет тестового компьютера, то проще всего будет создать себе изолированную систему для тестов, используя chroot. Сразу предупреждаю: создать полноценную систему с помощью chroot не получится, для этого применяется jail.
Давайте теперь попробуем запустить sh в chroot, для тестирования ПО.
Создаем папки для упешного применения chroot для sh:
# mkdir /usr/chroot/{bin,sbin,lib,libexec,usr}
# mkdir /usr/chroot/usr/{bin,sbin,lib,libexec}
Копируем нужные бинарные файлы:
# cp /bin/* /usr/chroot/bin/
# cp /sbin/* /usr/chroot/sbin/
# cp /usr/sbin/* /usr/chroot/usr/sbin/
# cp /usr/bin/* /usr/chroot/usr/bin/
Копируем необходимые библиотеки:
# cp -r /lib/* /usr/chroot/lib/
# cp /libexec/* /usr/chroot/libexec/
# cp -r /usr/lib/* /usr/chroot/usr/lib/
# cp -r /usr/libexec/* /usr/chroot/usr/libexec/
Запускаем sh в chroot:
# chroot /usr/chroot/ /bin/sh
Теперь посмотрим, что у нас в корне. Из вывода ls прекрасно видно, что настоящий корень – /usr/chroot.
# ls –la
total 16
drwxr-xr-x 7 0 0 512 Jun 1 21:49 .
drwxr-xr-x 7 0 0 512 Jun 1 21:49 ..
drwxr-xr-x 2 0 0 1024 Jun 1 21:49 bin
drwxr-xr-x 3 0 0 1024 Jun 1 21:49 lib
drwxr-xr-x 2 0 0 512 Jun 1 21:50 libexec
drwxr-xr-x 2 0 0 2560 Jun 1 21:49 sbin
drwxr-xr-x 6 0 0 512 Jun 1 21:49 usr
|
# pwd
Мы скопировали библиотеки не только для sh, но и для остальных утилит, которыми мы будем пользоваться, например ls и pwd.
Предыдущий пример использования chroot не очень удачный, так как chroot не создан для виртуализации системы. Для этого используется механизм jail, который является родным для FreeBSD, следовательно, продуман jail намного лучше, нежели chroot.
Jail
Механизм jail позволяет администратору делить полноценную FreeBSD-систему на маленькие подсистемы, которые называются jail (тюрьмами). В каждой такой «тюрьме» находятся свои процессы, файлы, аккаунты. Благодаря этому механизму можно увеличить безопасность системы и создать виртуальные машины.
Я бы не хотел повторяться, рассматривая примеры, которые были применены к chroot, поэтому сейчас я расскажу о том, как создать виртуальную машину с помощью jail.
Итак, в первую очередь необходимо настроить DNS-сервер. Так как статья посвящена безопасности, то настройку сервера имен я здесь не описываю. Но имена машин нам понадобятся.
Давайте определимся:
- Домен – UNIX.
- Компьютер, на котором производится установка виртуальной машины – satdevil.unix.(172.16.0.200).
- Виртуальная машина – evil.unix.(172.16.0.199).
- DNS-сервер – dns.unix.(172.16.0.1).
Чтобы понимать, на какой машине происходит действие, приглашение командного интерпретатора будет выглядеть следующим образом: «hostname#».
Создание виртуальной машины
Создаем директорию, где будет находиться виртуальная машина:
satdevil# mkdir /usr/local/evil
Собираем и устанавливаем «мир» в только что созданную директорию:
satdevil# cd /usr/src
satdevil# make world DESTDIR=/usr/local/evil
Теперь установите конфигурационные файлы, скрипты и т. д. в каталог:
satdevil# make distribution DESTDIR=/usr/local/evil
Чтобы настроить нашу виртуальную машину, запустим в ней /bin/tcsh. Общий вид команды jail выглядит так:
// jail <путь_к_новому_корню> <hostname> <ip_address> <команда>
команда указывается относительно нового корня.
satdevil# jail /usr/local/evil evil 172.16.0.199 /bin/tcsh
Теперь мы находимся в нашей новой системе. Для настройки можно воспользоваться sysinstall, но я буду использовать командный режим, а не графический.
Первым делом создаём пустой файл /etc/fstab, это нужно для предотвращения ошибок при загрузке системы.
evil# touch /etc/fstab
Теперь настроим автозагрузку.
Отключаем rpcbind, который на виртуальной машине не нужен:
evil# echo rpcbind_enable="NO" > /etc/rc.conf
Отключаем конфигурирование интерфейсов, дабы избежать ошибок, связанных с ifconfig:
evil# echo network_interfaces="" >> /etc/rc.conf
Включаем sshd для удаленного управления виртуальной машиной:
evil# echo sshd_enable="YES" >> /etc/rc.conf
Редактируем resolv.conf для нормального определения dns имен. Добавляем primary dns сервер и домен.
evil# echo domain Unix > /etc/resolv.conf
evil# echo nameserver 172.16.0.1 >> /etc/resolv.conf
Создаем базу алиасов для sendmail, опять-таки чтобы избежать ошибок при запуске sendmail:
evil# newaliases
Теперь настраиваем timezone. Для Москвы это будет выглядеть так:
evil# cp /usr/share/zoneinfo/Europe/Moscow /etc/localtime
Задаем пароль суперпользователю в виртуальной машине. Напоминаю, что root в виртуальной системе и root на основной системе – два совершенно разных пользователя, никаким образом не зависящих друг от друга.
evil# passwd
Changing local password for root
New Password:
Retype New Password:
|
Давайте создадим нового пользователя, для удаленного администрирования сервера:
evil# pw useradd fr33man -s /bin/tcsh -m -d /home/fr33man/
Зададим пароль пользователю:
evil# passwd fr33man
Changing local password for fr33man
New Password:
Retype New Password:
|
Выходим из виртуальной машины:
evil# exit
Теперь необходимо подготовить основную систему.
Прежде всего сделаем alias адреса 172.16.0.199 на адрес 172.16.0.200. rl0 – имя интерфейса с адресом 172.16.0.200:
satdevil# ifconfig rl0 inet alias 172.16.0.199/32
Создаем proc filesystem, необходимую для работы ядра и процессов:
satdevil# mount -t procfs proc /usr/local/evil/proc
Jail хорошо продумана во FreeBSD, поэтому запускать мы ее будем не из командной строки, как chroot, а с помощью специального скрипта, расположенного в /etc/rc.d/. При запуске скрипт считывает данные из /etc/rc.conf. Все действия производим на satdevil.
Добавляем в rc.conf:
// Включаем jail
jail_enable="YES"
// Список «тюрьм», находящихся на данной машине.
// Если тюрьм несколько, то перечисление их идет через пробел
jail_list="evil"
// Запрещаем смену hostnam в jail
jail_set_hostname_allow="NO"
// Разрешаем использовать процессам только домены
// PF_LOCAL, PF_INET, PF_ROUTE
jail_socket_unixiproute_only="YES"
// Далее идет описание конкретной «тюрьмы», в нашем случае это evil
// Задаем корневой каталог
jail_evil_rootdir="/usr/local/evil/"
// Указываем имя запускаемой виртуальной машины
jail_evil_hostname="evil.unix"
// Указываем IP-адрес виртуальной машины
jail_evil_ip="172.16.0.199"
// Указываем, что именно запустить в «тюрьме».
// Команда /bin/sh /etc/rc приведет к инициализации и запуску системы
jail_evil_exec="/bin/sh /etc/rc"
// Разрешаем использование файлов устройств
jail_evil_devfs_enable="YES"
После редактирования осталось только запустить jail. Делается это командой:
satdevil# /etc/rc.d/jail start
При первом запуске придется немного подождать, чтобы создались все необходимые файлы и запустились необходимые сервисы.
Вот и все. Мы настроили виртуальную машину. Теперь, чтобы попасть в нее, достаточно сделать следующее:
$ ssh evil.unix -l fr33man
Гланое – найти золотую середину
Jail можно использовать как и для разделения основной системы на несколько маленьких, так и для увеличения безопасности путем помещения заведомо опасных процессов в «тюрьму».
Если вы используете FreeBSD, то лучше использовать jail, чем chroot по нескольким причинам:
- Jail хорошо продуман во FreeBSD, и с его помощью, вы сможете решить любую задачу по изоляции процесса, вплоть до виртуализации системы.
- Chroot возможно обойти, да и продуман он не так хорошо, как jail.
Также не забывайте и о превосходной системе прав в UNIX, которая позволяет решить практически любую проблему, связанную с безопасностью. Тем более если процесс не работает от имени суперпользователя.
С jail тоже не следует переусердствовать: если у вас слишком много виртуальных машин, то проще парочку из них вынести на настоящие машины.
Главное – найти золотую середину.