Что не так с chroot: почему для контейнеров используется именно pivot_root

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Эта статья является переводом. В ней рассматриваются различия в результатах применения команд chroot «в лоб» и chroot, применённой после pivot_root. Это поможет разобраться, почему именно pivot_root используется в контейнеризации. Передаём слово автору.

Привет всем! В одной из предыдущих статей о безопасности Linux я упоминал о проблемах с chroot и о том, как злоумышленник может ими воспользоваться с помощью «техники двойного chroot'а». Команда разработчиков Docker решила отказаться от chroot и перейти на pivot_root, поскольку иногда при отладке в контейнере требуются привилегии суперпользователя (или разрешённый CAP_SYS_CHROOT), что делает chroot неподходящим вариантом.

Что такое pivot_root?

При запуске контейнеров в разных пространствах имён необходимо следить за безопасностью, чтобы два процесса, запущенные в разных пространствах имён, не конфликтовали друг с другом. Поскольку chroot применяется к активному процессу и его дочерним процессам, но не изменяет точку монтирования корневой директории (root) и таблицу монтирования в глобальном пространстве имён, из его «тюрьмы» легко вырваться и получить доступ к файловой системе хоста. Я рассказывал об этом в своей предыдущей статье о выходе за пределы chroot. Рекомендую с ней ознакомиться.

Изменение корневой директории во внешнем пространстве имён чревато кучей проблем, поскольку процессы обычно не рассчитывают на внезапное изменение своей корневой директории. Я не проверял это на практике и не совсем уверен, но всё же полагаю, что именно так и будет.

runc также создаёт новое пространство имён mount, после чего выполняет в нём pivot_root, меняя корневую директорию. Точнее, он изменяет корень в новом пространстве имён на new_root и перемещает старый корень за пределы пространства имён в каталог put_old. То есть если поискать корневую директорию процесса вне пространства имён, то она будет ссылаться на /, а не на более глубокую иерархию в файловой системе, подобную той, которая получилась бы с chroot.

Не волнуйтесь, если пока не понимаете, о чём речь. Это лишь теоретическая часть. Уверен, вы во всём разберётесь, когда я расскажу о проблемах с chroot.

Проблемы с chroot в контейнерах

В заметке «Управление ресурсами Docker в деталях» я показал, как с помощью chroot можно создавать контейнеры без использования runc или containerd. Но достаточно ли этого? А как насчёт безопасности и побега из chroot-окружения? Что ж, давайте посмотрим.

Создайте файл secret вне пространства имён и сохраните его в /bin/secret. В него можно записать что угодно. Я воспользовался $RANDOM$RANDOM, чтобы не тратить время на размышления:

Создайте файл secret за пределами пространства имён
Создайте файл secret за пределами пространства имён

Теперь переключитесь в chroot-окружение и попробуйте вывести список файлов в директории /bin. В нём не будет никакого файла secret. Это совершенно логично, поскольку для текущего процесса и его дочерних процессов корневой каталог изменён, /bin/ в chroot больше не указывает на оригинальный /bin/ за пределами пространства имён.

Проверяем версию Python
Проверяем версию Python

Python уже установлен в пространстве имён, а оболочка запущена от имени суперпользователя. Теперь скопируйте эксплойт из моей статьи и вставьте его в новый файл:

import os

if not os.path.exists("chroot"):
	os.mkdir("chroot")
os.chroot("chroot")

for _ in range(1000):
	os.chdir("..")

os.chroot(".")
os.system("/bin/bash")

Эксплойт запустит Bash в корневой директории вне пространства имён, несмотря на ограничения chroot. Результат будет похож на скриншот ниже:

Доступ к файлу secret за пределами окружения контейнера
Доступ к файлу secret за пределами окружения контейнера

Этот второй chroot возможен, потому что пользователь по сути работает под UID суперпользователя из-за флага -r/--map-root-user в unshare. Он нужен, чтобы примонтировать procfs в /proc.

Я поленился всё настраивать в AttackDefense и воспользовался следующей командой для создания уязвимого пространства имён (без контрольных групп):

unshare --mount --pid --user --map-root-user --fork \
--mount-proc chroot dockerfs sh -c "mount -t proc proc /proc && bash"

Дочерний процесс в этом пространстве имён, который является самым первым процессом sh, естественно, наследует текущую рабочую директорию и корневую директорию от родительского процесса. Но если посмотреть на тот же процесс снаружи пространства имён, то и корень, и текущая рабочая директория (cwd) будут привязаны к /home/terabyte/dockerfs, а не к /.

Корень и cwd процесса внутри пространства имён и вне него
Корень и cwd процесса внутри пространства имён и вне него

Исправляем уязвимость с помощью pivot_root

Для успешного выполнения pivot_root необходимо запустить новый Bash в пространстве имён без chroot, настроить procfs и сделать bind mount для dockerfs, заменив корень внутри пространства имён mount.

Создание пространства имён и монтирование procfs и dockerfs
Создание пространства имён и монтирование procfs и dockerfs

Также необходимо, чтобы и new_root, и put_old были директориями в файловой системе, причем директория, ссылающаяся на put_old, должна быть дочерней для new_root. Можно создать директорию в dockerfs, а затем сделать pivot_root, как показано ниже. После этого текущая директория станет корневой в таблице монтирования внутри пространства имён.

pivot_root в действии
pivot_root в действии

Если заглянуть в директорию .oldroot, то она теперь будет содержать старую корневую файловую систему, и эта информация также будет отражена в таблице монтирования внутри пространства имён, причём корневая директория будет заменена на /.oldroot. Разве это не странно?

Изменения в новом пространстве имён mount, старый корень «помещён»  в .oldroot
Изменения в новом пространстве имён mount, старый корень «помещён»  в .oldroot

Процесс вне этого пространства имён работает от имени пользователя с низкими привилегиями (terabyte), поэтому любые действия с высокими привилегиями будут запрещены, но он всё равно сможет выполнять чтение и запись в домашнюю директорию пользователя (/home/terabyte). Это всё ещё не та истинная изоляция, к которой мы стремимся.

Действия в старом корне, требующие привилегий, будут отклонены
Действия в старом корне, требующие привилегий, будут отклонены

Поскольку новая rootfs уже готова, можно отмонтировать .oldroot, в результате чего директория станет пустой. Чтобы всё заработало, перемонтируйте procfs в директорию /proc — она будет использоваться для получения таблицы монтирования внутри пространства имён. После этого можно безопасно выполнить chroot в текущую файловую систему, и на этот раз старый эксплойт не сработает.

Отмонтируйте старую корневую файловую систему и выполните chroot на корень текущей файловой системы
Отмонтируйте старую корневую файловую систему и выполните chroot на корень текущей файловой системы

В таблице монтирования теперь будут только эти две записи (нет никакого способа — по крайней мере, известного мне — вернуться к старым точкам монтирования):

/dev/nvme0n1p3 on / type ext4 (rw,noatime)
proc on /proc type proc (rw,relatime)

Заключение

Подведем итог. Команда chroot изменяет корневую директорию для активного процесса и его дочерних процессов, но не изменяет точку монтирования корневой директории и таблицу монтирования в глобальном пространстве имён, что создаёт угрозу безопасности. pivot_root изменяет корневую директорию в новом пространстве имён и перемещает старый корень за пределы пространства имён, что обеспечивает лучшую изоляцию и безопасность.

P. S.

Читайте также в нашем блоге:

Источник: https://habr.com/ru/companies/flant/articles/880342/


Интересные статьи

Интересные статьи

Я изучаю Битрикс где-то пару недель. Зачем?.. Хотелось чего-то новенького, тут подвернулась учёба. Даром, с наставниками, с возможным трудоустройством дальше хотя бы на пару месяцев - на испытательный...
Привет, мы команда СберМегаМаркета, и это обзорная статья о нашей площадке, пробный камень для блога Хабре. За нашими плечами спешный переезд с PHP на GO, ребрендинг и решение таких задач, с которыми ...
Очень часто при работе мы обращаем внимание на то, что все люди знают, что такое распознавание речи, но не знают, что такое Voice Activity Detector (VAD) или детектор речи. А ведь именно VAD на с...
Привет, Хабр! Вот уже пару лет работаю с иностранными заказчиками, поэтому решил поделиться наработанным опытом. В статье постараюсь рассмотреть проблемы работы с иностранными заказчиками, достоинства...
Хочу поделиться опытом автоматизации экспорта заказов из Aliexpress в несколько CRM. Приведенные примеры написаны на PHP, но библиотеки для работы с Aliexpress есть и для...