Казалось бы, учетная запись в GitLab — не самая удачная стартовая точка для инфраструктурного пентеста. Ни VPN в корпоративную сеть, ни доменного аккаунта, ни даже RDP на рабочую станцию, только креды рядового разработчика. Вряд ли в начале проекта заказчик ожидал серьезного импакта, но мы доказали, что при типовых настройках CI/CD такая учетка находится на расстоянии нескольких прыжков до контроля над облаком. 

Давайте вместе пройдем эту цепочку.

Начальная точка: учетка в GitLab

Итак, на старте у нас была только учетная запись разработчика в GitLab. Мы зашли в систему, создали тестовый проект и добавили в него .gitlab-ci.yml — файл, который описывает логику пайплайна.

fig:
Наш .gitlab-ci.yml

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

Прежде всего нужно было понять, сможем ли мы вообще запускать произвольные сборки на раннерах и, если да, то с какими правами. Результат не заставил себя ждать: 

fig:

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

  1. Мы действительно можем запускать произвольные команды на раннере PROD-K8S-RUNNER01.

  2. Раннер успешно достучался до нашего внешнего вебхука.

  3. Код на раннере выполняется от имени пользователя gitlab_runner.

  4. Пользователь gitlab_runner входит в группу docker.

Вывод команды id:

uid=999(gitlab-runner) gid=999(gitlab-runner) groups=999(gitlab-runner),112(docker)

Последний факт — крупная удача, так как членство в группе docker в Linux‑окружении дает прямой путь повышения привилегий до root. Дело в том, что сокет Docker (/var/run/docker.sock) принадлежит к этой группе, и любой ее член может запускать контейнеры с любыми параметрами namespaces. Поэтому формально у нас права рядового раннера, а фактически — заявка на весь хост.

От gitlab_runner к root 

Самый надежный способ повысить привилегии в такой ситуации — запустить контейнер с монтированной файловой системой хоста и добавить наш SSH‑ключ в /root/.ssh/authorized_keys.

Проблема в том, что команды через .gitlab-ci.yml выполняются неинтерактивно, в рамках одного пайплайна. Это создает определенные сложности, да и хотелось побыстрее уйти от выполнения кода через сборки. Поэтому мы предварительно развернули на раннере C2-маяк, он связался с нашим управляющим сервером, а дальше мы работали уже по C2-каналу:

docker run --rm -v /:/host alpine sh -c "echo 'ssh-rsa AAAAB3NzaC1yc2E...' >> /host/root/.ssh/authorized_keys"
fig:

Пара минут, и мы заходим на PROD-K8S-RUNNER01 как root (добраться до 22-го TCP‑порта помог тот же C2-канал).

fig:

Лутинг на хосте

Мы не стали спешить с запуском скриптов для автоматического сбора секретов и сначала вручную осмотрели переменные окружения (env), домашние директории пользователей и обычные места хранения конфигов Kubernetes.

В домашней директории одного из пользователей лежал файл config — обычный kubeconfig с токеном пользователя CICD для кластера prod-cluster-k8s.

fig:

Сразу проверили, есть ли на хосте kubectl, и, найдя искомое, выполнили проверку прав:

kubectl --kubeconfig=config auth can-i --list

В вывод попали:

  • secrets с get и list, которые позволяют чтение или изменение секретов;

  • pods/exec с create, позволяющие выполнять команды внутри подов;

  • pods/portforward и services/proxy с create, позволяющие пробрасывать трафик во внутренние сервисы и обходить сетевые политики;

  • bindings с create — позволяет связывать роль с группой или сервисным аккаунтом. Тривиальный способ получить cluster-admin.

По сути, этот набор прав дает полный контроль над кластером. 

Переход на ноду K8s

Чтобы перейти к исследованию нод кластера, мы создали манифест и подняли под с примонтированной файловой системой хоста. 

fig:

Зайдя в под, получили доступ к файловой системе ноды PROD-K8S01-MS1-MAIN-1.

fig:

Проверяя директорию /etc/kubernetes, мы обнаружили то, что искали — некий cloud-config. Анализ конфига показал, что речь идет об OpenStack облаке. 

Переход в облако

Подготовив клиент OpenStack, мы выполнили команды:

openstack server list
openstack network list
openstack user list
fig:

И в результате перечислили роли полученной учетной записи.

Роли admin в списке нет, но в данном случае это не имеет значения: набор ролей учетки дает настолько широкие права, что де‑факто это администратор облака.

Имя роли

Возможности роли

Импакт

nova

Полное управление инстансами ВМ (создание, удаление, остановка, перезагрузка, изменение флейвора, доступ к консоли)

Можно поднять свою ВМ, украсть данные, запустить майнер, атаковать соседей по облаку

cinder, cinderv2/v3

Управление дисками (томами) и снимками

Позволяет красть и уничтожать данные, подключать чужие тома, читать закрытую информацию

glance

Работа с образами ОС

Загрузить бэкдорированный образ, подменить существующий, распространить вредоносное ПО

neutron

Управление сетями, подсетями, роутерами, security groups

Полный контроль сетевой изоляции, перехват трафика, открытие доступа извне, подмена DNS

octavia

Управление балансировщиками нагрузки

Перенаправить трафик на себя, снять нагрузку с целевых серверов (DoS)

heat

Работа с оркестрацией (стэки)

Задеплоить вредоносную инфраструктуру одной командой, используя доверие платформы

magnum

Управление кластерами Kubernetes в OpenStack

Создать свой кластер для майнинга, украсть учетные данные от других кластеров, атаковать cloud‑контроллер

managed-k8s (кастомная)

Скорее всего, роль администратора в управляемых кластерах K8s

Позволяет управлять кластерами Kubernetes заказчика, которые работают на этом OpenStack

placement

Управление пулами ресурсов

Узнать, где какие ресурсы лежат, влиять на планировщик

Итоги

Полная цепочка компрометации умещается в пару строк: учетка разработчика в GitLab → запуск сборки на раннере → группа docker → root на хосте → kubeconfig → доступ к Kubernetes → повторное монтирование ноды через под → учетные данные OpenStack → полный контроль над облаком.

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

Что с этим делать на практике:

  • держать CI/CD‑раннеры вне группы docker и без доступа к хостовому Docker‑сокету в изоляции, по возможности rootless;

  • не хранить рабочие kubeconfig'и и облачные конфиги на раннерах и хостах;

  • ограничивать права сервисных токенов (CICD и подобных) по RBAC до минимума и делать их короткоживущими;

  • запрещать привилегированные поды и hostPath политиками кластера;

  • выдавать узлам облачные учетки строго под задачу и регулярно аудировать роли.


PURP — Telegram-канал, где кибербезопасность раскрывается с обеих сторон баррикад

t.me/purp_sec — инсайды и инсайты из мира этичного хакинга и бизнес‑ориентированной защиты от специалистов Бастиона

Комментарии (0)