Под защитой песочного демона

Евгений Зобнин
Хакер, номер #093, стр. 093-118-4

Особенности функционирования ssh в chroot-окружении
Когда возникает вопрос организации доступа к удаленной машине, лучшего решения, чем ssh , не найти. Как волшебная палочка, ssh делает друзей гостями, а недругов выставляет за дверь. Но в неумелых руках волшебство может превратиться в черную магию и обернуться против своего хозяина. Добрый волшебник Мэрлин расскажет, как не допустить такое превращение и уберечь себя от того, что скрывается внутри ящика Пандоры.

[предисловие]
Продолжая тему изолированных окружений времени исполнения или попросту "песочниц", предлагаю твоему вниманию очередную статью в этом, уже можно сказать, цикле. В прошлый раз мы рассмотрели преимущества технологии jail , средства для создания виртуальных серверов в ОС FreeBSD. В том материале акцент был сделан на самой технологии виртуализации, а пример с ssh приведен только для демонстрации практической пользы jail-окружений. Сегодня мы немного изменим круг наших интересов (если, конечно, никто не против) и поместим в его центр сам ssh-сервер, а chroot-окружение будем использовать только как средство достижения заветных целей. Кроме того, в качестве рабочей платформы сегодня будет выступать Linux, лидер по количеству продаж и установок :).

Перед нами стоит задача организации удаленного доступа к нашей машине. Мы должны раздавать аккаунты недоверенным пользователям в большом количестве и без проверки личной информации. Само собой, предоставление доступа такому контингенту потребует особых мер защиты, потому как трудно определить истинные намерения человека только по его имени и IP-адресу :). Правильной настройки ssh-сервера и брандмауэра в данном случае не достаточно, необходимо ограничение пользователя уже внутри самой системы. Поэтому лучшим решением будет помещение юзеров в изолированную среду, chroot-окружение, с обрезанием всего, что может быть использовано в корыстных целях.
[свобода свободе рознь]
Итак, вооружившись знаниями и взяв в руки флаг "Не допустим браконьерства!", отправляемся на защиту наших рубежей. Во-первых, следует определить, какие команды могут понадобиться пользователям после того, как они окажутся внутри chroot . В зависимости от того, для каких целей организовывается доступ, это может быть предоставление доступа клиентам хостинга к их страничкам, хранение важной информации или банальный прокси, вариантов много. Поэтому стоит сразу обдумать, какие компоненты ОС будут доступны клиентам.

Создание chroot-окружения начинается с организации минимальной каталоговой структуры внутри выделенного каталога и копирования туда программ, о которых мы уже подумали, а также необходимых для их нормальной работы библиотек (проверяется с помощью /usr/bin/ldd ). Кроме того, понадобятся некоторые элементы каталога /dev . Копировать все это руками крайне неинтересно, поэтому мы воспользуемся небольшим скриптом, который сделает грязную работу за нас:

vi ~/bin/mkchroot

#!/bin/sh
CHROOT=$1

# какие команды мы хотим предоставить пользователям?
CMDS="/bin/sh /bin/bash /bin/ls /bin/cp /bin/mv /bin/rm /bin/mkdir /usr/bin/id"

# создаем каталог для chroot-окружения
mkdir -p $CHROOT
chown root:root $CHROOT
chmod 700 $CHROOT
cd $CHROOT

# создаем каталоговую структуру
DIRS="bin sbin lib etc dev home usr usr/bin /usr/sbin usr/lib"
for dir in $DIRS; do
mkdir -p $dir
done

# копируем необходимые библиотеки
LIBS=`ldd $CMDS|grep -v ':$'|grep -v "not a dynamic executable"|cut -f 3 -d " "|sort|uniq|sed 1d`
for lib in $LIBS; do
cp -P $lib ./$lib
cp -L $lib ./$lib
done
cp -P /lib/ld-linux.so.2 ./lib
cp -L /lib/ld-linux.so.2 ./lib

# копируем команды
for cmd in $CMDS; do
cp -P $cmd ./$cmd
cp -L $cmd ./$cmd
done

# создаем необходимые файлы устройств
mknod -m 666 dev/null c 1 3
mknod -m 666 dev/zero c 1 5
Ставим на скрипт бит исполнения и запускаем:
# chmod +x ~/bin/mkchroot
# ~/bin/mkchroot /usr/chroot/ssh
Каталог /usr/chroot/ssh волшебным образом наполнится почти всем, что может только понадобиться.

Следующий шаг - перенос ssh .
Потребуется скопировать сам демон /usr/sbin/sshd , файл /usr/libexec/sftp-server (если требуется sftp-сервер), конфигурационные файлы /etc/ssh/{moduli,ssh_config,sshd_config} . Далее следует создать два каталога внутри chroot-окружения: /var/empty (без него sshd не сможет понижать свои привилегии до пользователя sshd), /var/run (для хранения PID-файла) и несколько дополнительных файлов устройств: /dev/urandom, /dev/ptmx, /dev/ptyp*, /dev/ptyq*, /dev/ttyp*, /dev/ttyq* (нужны для работы псевдотерминала). Проще будет скопировать их из корневой машины. Кроме того, рекомендуется перенести каталог /usr/share/terminfo для правильной инициализации терминала (иначе многие клавиши будут работать неверно). Осталось скопировать библиотеки, и перенос sshd можно считать завершенным. Прием с ldd в данном случае не пройдет, так как sshd загружает некоторые библиотеки "на лету". Чтобы узнать о всех (с оговорками) требуемых библиотеках, потребуется запустить sshd на корневой машине и выполнить следующую команду:
$ cat /proc/`cat /var/run/sshd.pid`/maps
Сервер ssh становлен и готов к запуску.

Следующий шаг - создание ключей и клиентских аккаунтов. Первая процедура достаточно тривиальна и выполняется несколькими простыми командами:
# cd /usr/chroot/ssh

# ssh-keygen -t dsa -f etc/ssh/ssh_host_dsa_key -N ''

# ssh-keygen -t rsa -f etc/ssh/ssh_host_rsa_key -N ''
С аккаунтами немного сложнее. Дело в том, что стандартные утилиты, манипулирующие привилегиями пользователя, не позволяют изменить месторасположение файлов /etc/{passwd,shadow,group} . Эта проблема может быть решена различными способами. Можно скопировать эти файлы в укромное место, создать на их месте пустые файлы, добавить нужные аккаунты для пользователей, переместить файлы в chroot , восстановить оригиналы. Можно установить в chroot утилиты /usr/sbin/{passwd,useradd,userdel,usermod} (для этого достаточно добавить их в переменную CMDS вышеприведенного скрипта), зайти в сhroot под рутом и добавить пользователей. В конце концов никто не запрещает править файлы в ручную, как это делаю я. Главное создать пользователей root и sshd, а также группы root, sshd и sshusers, в последнюю мы будем помещать наших пользователей.

Последний штрих - изменяем значение опции "UsePAM" на "no" в файле /usr/chroot/ssh/etc/ssh/sshd_config . Теперь все, можно со спокойной душой запускать сервер:
# chroot /usr/chroot/ssh /usr/sbin/sshd

[нестандартные решения стандартных проблем]
К сожалению, описанный выше способ имеет один серьезный недостаток, проявляющийся в том, что теперь невозможно получить ssh-доступ к корню. В случае с jail-окружением эта проблема решается чуть ли не автоматически благодаря виртуализации сетевых ресурсов. В данной же ситуации мы не можем запустить второй ssh-сервер на корневой машине, две программы технически не способны разделять один порт. Само собой напрашивается решение - изменить значение опции "Port" одного из ssh-серверов на номер другого, незанятого порта. На мой взгляд, не совсем удачный ход, путаницы и так хватает (а способ защиты на основе смены порта довольно наивен). А что если сделать так, чтобы корневой ssh-сервер делал системный вызов chroot и отправлял указанных пользователей в изолированную среду? Несмотря на всю абсурдность и противоречивость такой идеи, она является наиболее популярной.

План следующий - патчим sshd таким образом, чтобы он, встретив в базе пользователей некий знак, делал chroot и отрезал юзера от корня. Идея настолько проста и примитивна, что любой человек, обладающий навыками программирования, способен исправить sshd и придать ему нужную функциональность. Но ничего кодить не придется, об этом уже позаботился человек из проекта chrootssh (chrootssh.sf.net) . Все, что от нас требуется - это наложить на сoрцы OpenSSH патч osshChroot-4.3p1.diff или скачать тарболл openssh-4.2p1-chroot.tar.gz с предварительно пропатченными исходниками, а затем произвести сборку и установку модифицированной версии.

Далее вышеприведенным скриптом потребуется создать chroot-окружение для будущих юзеров. Самих chroot'ных клиентов мы создадим тоже в корне и поместим их в группу sshusers. Причем в поле домашнего каталога этих юзеров необходимо указать каталог "/usr/chroot/ssh/./home/user" (точка - это и есть та самая метка, на которую sshd реагирует, как собака на кость, и отправляет пользователя в глубокий chroot , а именно - в каталог, указанный перед точкой). И это все, осталось только перенести некоторые строки из базы пользователей корня в chroot-окружение:
# cd /usr/chroot/ssh
# grep /etc/group -e "^root" -e "^sshusers" > etc/group
# grep /etc/passwd -e "^root" >> etc/passwd
# grep /etc/passwd -e "^user" >> etc/passwd
Последнюю команду придется выполнять каждый раз после добавления новых клиентов в группу sshusers.
Другой и еще более грязный способ предложил Wolfgang Fuschlberger.
Скрипт, который можно скачать с его сайта (www.fuschlberger.net), подготавливает chroot-окружение таким образом, что выбранные пользователи автоматически попадают в chroot, причем реализуется это стандартными средствами. Как же он работает?
Создается стандартное jail окружение и в него, кроме всего прочего, копируется /bin/su .

В каталог /bin корневой машины ложится файл ssh-shell, примерно такого содержания:
!#/bin/sh
/usr/bin/sudo /usr/bin/chroot /usr/chroot/ssh /bin/su - $USER "$@"
В файл /etc/sudoers корневой машины добавляется запись следующего вида:
# visudo

user ALL=NOPASSWD: /usr/bin/chroot, /bin/su - user
Также в корневую машину добавляется юзер с домашним каталогом /usr/chroot/ssh/home/user и группой sshuser, причем в качестве шелла ему назначается /bin/ssh-shell .
В файл /etc/passwd chroot-окружения добавляются пользователи root и user, а в файл /etc/group группа root и sshusers.

В результате вырисовывается очень интересная картина. После регистрации пользователь, у которого в качестве шелла указан /bin/ssh-shell , сам того не подозревая, выполняет прописанные в этом файле команды и попадает в chroot-окружение, а все остальные могут ходить по корню. Такой вот пример костыльно-велосипедного подхода, демонстрация "пути линуксойда" во всей красе. Лучше уж патчить OpenSSH.
[PAM]
Хотя нет, погодите, ведь главная задача ssh, помимо шифрования трафика, авторизация пользователей. А эта процедура в последние годы неразрывно связана с технологией PAM. Может быть мы сможем использовать ее возможности в наших целях?

PAM-модуль с нужной нам функциональностью действительно существует и входит в поставку многих дистрибутивов. Называется он pam_chroot, а принцип его работы прост, как сапог - он делает chroot для всех пользователей, прописанных в конфиге. Кроме того, способ, основанный на использовании этого модуля, является наименее трудозатратным из всех ранее изложенных.

Смотри сам:
Создаем "песочницу", используя скрипт ~/bin/mkchroot.
Создаем группу sshusers и заводим пользователей.
Выполняем две команды (вторая должна быть продублирована для каждого юзера):
# echo "session required pam_chroot.so" >> /etc/pam.d/ssh
# echo "user /usr/chroot/ssh" >> /etc/security/chroot.conf
Перезапускаем sshd.

Правда есть один маленький нюанс. Системный вызов chroot может исполнять только root, поэтому sshd до самого логина пользователя должен работать с соответствующими правами. По умолчанию sshd время от времени понижает свои привилегии до пользователя sshd, но это можно исправить, добавив в конфиг строку "UsePrivilegeSeparation no".

[огнеупорный ssh ]
Поместить ssh в chroot-окружение и обеспечить его бесперебойную работу - еще половина дела. Необходимо также правильно настроить брандмауэр и защитить все остальные сервисы от проникновения из вне. Ниже приведен фрагмент конфига iptables (eth1 - это внешний сетевой интерфейс).
Настраиваем брандмауэр :
IT='/usr/sbin/iptables'

# очищаем правила
$IT -F
$IT -P INPUT DROP
$IT -P FORWARD DROP
$IT -P OUTPUT DROP

# пропускаем все пакеты наружу
$IT -A OUTPUT -j ACCEPT

# принимаем все пакеты с интерфейса обратной петли
$IT -A INPUT -i lo -d 127.0.0.1 -p ALL -j ACCEPT

# принимаем трафик установленных соединений
$IT -A INPUT -i eth1 -m state --state ESTABLISHED,RELATED -j ACCEPT

# открываем доступ ssh -серверу
$IT -A INPUT -i eth1 -p tcp --destination-port 22 -j ACCEPT

[подводя итог]
Каждый из приведенных в статье способов защиты имеет свои достоинства, но не один из них не лишен недостатков.
  •   Пример с помещением демона sshd прямо в chroot-окружение блестяще работает до тех пор, пока не потребуется получить доступ к корневой машине.
  •   Chrootssh на отлично справляется со своей задачей и прост в настройке, но требует пересборки пакета OpenSSH.
  •   Скрипт от Wolfgang Fuschlberger прост в использовании, но создан с использованием садомазохистских приемов.
  •   Модуль pam_chroot обладает особым изяществом, требуя, чтобы ssh-сервер всегда работал с правами администратора.


  • По какому пути пойти, решай сам, я свою задачу выполнил, расписав все известные мне подходы.

    [Linux must die?]
    Какой еще chroot? Почему в Linux нет настоящей виртуализации? Думаю, эти вопросы интересуют многих. Действительно, у поклонников FreeBSD есть jail , любителям операционной системы, названой в честь фантастико-философского произведения Станислава Лема, досталась еще более продвинутая технология zones, а линуксоидам приходится довольствоваться примитивной песочницей. К счастью, этот недостаток можно компенсировать за счет сторонних разработок. Например, аналог jail присутствует в RSBAC (www.rsbac.org), сходную функциональность можно получить, наложив на ядро специальный патч (kerneltrap.org/node/view/3823), реализованный с использованием технологии LSM (Linux Security Modules). Также не стоит забывать о том, что ядро Linux уже давно научилось работать поверх виртуальной машины XEN (о ней читай в моей статье "Искусство виртуализации"), а это открывает поистине безграничные возможности для виртуализации.


    Note
    Хорошей идеей будет вообще отказаться от копирования библиотек и слинковать программы статически.
    Чтобы помещенный в chroot-окружение sshd мог беспрепятственно вести логи, демон syslog должен быть запущен с опцией '-a /usr/chroot/ssh/dev/log'.