Kubernetes LAN Party write-up
Совсем недавно в преддверии KubeCon + CloudNativeCon Europe 2024 исследователи из компании Wiz опубликовали для всех желающих небольшой онлайн CTF “Kubernetes LAN Party” на тему Kubernetes Security. При этом ничего дополнительно устанавливать не требуется, терминал доступен из браузера.
Наша команда Luntry не могла пройти мимо такой активности и с удовольствием приняла участие! На все про все у нас ушло около часа, и теперь мы готовы поделиться с вами информацией о прохождении всех заданий. НО если вы еще сами не попробовали их решить, то рекомендуем сначала проверить собственные силы, а потом уже обратиться к этой статье.
Итак, приступим — впереди наш ждет 5 увлекательных задачек 😉
1. DNSing with the stars [Recon]
You have shell access to compromised a Kubernetes pod at the bottom of this page, and your next objective is to compromise other internal services further. As a warmup, utilize DNS scanning to uncover hidden internal services and obtain the flag. We have “loaded your machine with dnscan to ease this process for further challenges.
Все флаги в этом CTF имеют следующий формат: wiz_k8s_lan_party{}
По описанию задания требуется найти сервис, запущенный в Kubernetes. Он и должен отдать нам флаг. В Kubernetes важную роль играет DNS: он необходим для Service Discovery. Нужный нам сервис будем сканить как раз через DNS.
Но для начала узнаем podCIDR. Например, это можно сделать так:
- Посмотреть адрес KubeAPI через env
- Посмотреть значение, выставленное в файле /etc/resolv.conf
Также в описании таска указано, что авторы положили внутрь контейнера утилиту dnscan для сканирования по DNS. Попробуем ей воспользоваться, указав маску подсети /24. Скан не принес никаких результатов, попробуем расширить маску подсети и увеличить число хостов до 65536:
Был обнаружен сервис getflag-service.k8s-lan-party.svc.cluster.local, попробуем до него достучаться:
Забираем первый флаг.
2. Hello? [Finding Neighbours]
Sometimes, it seems we are the only ones around, but we should always be on guard against invisible sidecars reporting sensitive secrets.
Авторы недвусмысленно намекают нам о наличии sidecar контейнера, остается только найти его. Несколько контейнеров в одном Pod по дефолту могут разделять между собой Volumes, Inter-process communications (IPC) и сеть. Сам CTF задуман как Kubernetes Network Security челлендж, поэтому в первую очередь будем смотреть сеть.
Запустим сканирование по аналогии с предыдущим таском:
Находим сервис, пробуем достучаться до него:
Никакого ответа не получаем, пробуем еще раз, на этот раз выставив флаг -v:
Получили headers, но флаг нам не отдается. Попробуем послушать все активные сетевые соединения в контейнере:
Становится понятно, что наш контейнер постоянно делает запросы к IP, который мы только что нашли. Попробуем перехватить эти запросы, для этого воспользуемся tcpdump. Спустя пару запросов кроме заголовков мы также увидим флаг:
3. Exposed File Share [Data Leakage]
The targeted big corp utilizes outdated, yet cloud-supported technology for data storage in production. But oh my, this technology was introduced in an era when access control was only network-based.
Скажем так, иногда излишняя задумчивость – это не очень хорошо. В описании говорят о data storage, поэтому смотрим, что примонтировано в наш контейнер:
Среди всего прочего и привычного в глаза бросается следующее:
Посмотрим содержимое этой директории:
Флаг есть, но прочитать его мы не можем – у нас недостаточно прав. Чтобы понять, как двигаться дальше, необходимо определиться, в каком мы окружении и с чем имеем дело. Судя по неймингу хранилища, мы находимся в облаке AWS, и наше смонтированное хранилище есть ничто иное, как Amazon EFS. Изучив документацию, мы видим, что доступ к файлам хранилища реализован через ACL. Это значит, чтобы прочитать файл, нам нужно лишь указать нужный UID/GID.
Осталось понять, как взаимодействовать с хранилищем.
На просторах интернета находим инструмент nfs-cat, который должен помочь решить нашу задачу. Благо он уже есть в контейнере. Запускаем его с параметрами UID/GID=0 и выставленной версией 4 (есть еще 3):
Получаем флаг.
Но есть и альтернативный способ решения этого таска.
В документации сказано:
When you create a user on an EC2 instance, you can assign any numeric user ID (UID) and group ID (GID) to the user. The numeric user IDs are set in the /etc/passwd file on Linux systems. The numeric group IDs are in the /etc/group file. These files define the mappings between names and IDs. Outside of the EC2 instance, Amazon EFS doesn’t perform any authentication of these IDs, including the root ID of 0.
По факту это означает, что если мы сможем выставить нужный UID/GID, то у нас будет доступ к файлу. Изначально была идея создать фейковый usernamespace, так чтобы UID/GID был равен 1. Пробуем создать через unshare, изменить параметры ядра, но ничего не выходит, поскольку единственными разрешенными capability в контейнере являются CAP_NET_ADMIN/CAP_NET_RAW – для наших целей они не очень подходят. Также была идея изменить UID/GID у файла на лету по сети, но попытки перехватить трафик и модифицировать пакеты ни к чему положительному не привели.
Спустя n-ое время рассуждений возникает вопрос: как можно получить доступ к EFS вне контейнера? Ответ – нужна переадресация трафика.
Простейший ssh-тунель для передадресации может выглядеть так:
ssh -L 2049:192.168.124.98:2049 -o StrictHostKeyChecking=no -N root@<vps_ip>
Адрес 192.168.124.98 был виден при выполнении команды mount, а 2049 – это порт по умолчанию для службы NFS. Таким образом, мы можем успешно смонтировать новую директорию /efs под учетной записью root, выполнив mount на нашей тачке и без проблем прочитать флаг.
4.The Beauty and The Ist [Bypassing Boundaries]
Apparently, new service mesh technologies hold unique appeal for ultra-elite users (root users). Don’t abuse this power; use it responsibly and with caution.
Описание, название таска и логотип явно говорят нам о том, что мы имеем дело с Istio. Также к таску приложена следующая AuthorizationPolicy:
По сути, политика запрещает обращаться к интересующему нас сервису из текущего неймспейса. Просканируем сеть, найдем нужный сервис, попробуем к нему обратиться и получим ожидаемую ошибку:
Istio заблокировал наш запрос. Однако мы можем заабузить правила IPTables, которые Istio использует для обеспечения сетевых ограничений. Сменив наш идентификатор пользователя (uid) на 1337, мы сможем обойти ограничения Istio и получить доступ к нужному сервису.
5. Who will guard the guardians? [Lateral Movement]
Where pods are being mutated by a foreign regime, one could abuse its bureaucracy and leak sensitive information from the administrative services.
По заданию нам нужно воспользоваться мутирующей функциональностью Kyverno, чтобы получить флаг. В кластере работает следующая политика:
Соответственно, нам нужно создать Pod в неймспейсе sensetive-ns и получить флаг в переменных окружения Pod. Давайте проверим, какие права в кластере у нас есть:
Необходимых прав на создание Pod нет. Погрузимся немного в теорию, чтобы понять, как действовать дальше.
При создании какого-либо ресурса в Kubernetes он проходит через цепочку определенных фаз, которую называют Admission Controller Phases. Нас интересует стадия Mutating admission: именно в этот момент наш ресурс (а точнее AdmissionReview) отправляется на вебхук (Kyverno), и нам возвращается ответ. Отправить ресурс через KubeAPI, так чтобы он дошел до стадии Mutating admission, возможности и прав в кластере у нас нет. Значит, будем пробовать отправлять AdmissionReview напрямую в вебхук Kyverno.
Для начала нужно понять, куда отправлять данные. Для этого запустим скан:
Мы получили набор из 5 сервисов. Первые 4 нам не интересны – они отвечают за отдачу метрик и прочий функционал, который нам не понадобится.
У нас есть адрес: kyverno-svc.kyverno.svc.cluster.local
Мейнтейнеры Kyverno явно не задумывались о том, что пользователь будет напрямую общаться с Kyverno. Так что для того, чтобы найти нужную для нас ручку надо немного почитать исходники:
Судя по комментариям, нас интересует ручка /mutate. Осталось сформировать payload. Это можно сделать руками, но удобнее всего будет воспользовать инструментом kube-review. На вход он принимает привычный YAML-манифест, а отдает AdmissionReview в формате JSON. Не забываем, что Pod должен находится в namespace sensitive-ns, это единственное обязательное условие.
Полученный payload советуем разместить где-то на общедоступном сервере, потому что скопировать его без проблем в браузерный терминал не получится. Для таких целей идеально подойдет Github Gist, куда мы и загрузили наш файл.
Возвращаемся к нашему Pod, скачиваем payload:
Итак, у нас все готово для того, чтобы отправить запрос на Kyverno:
Получаем следующий ответ:
Нас интересует поле response, а точнее patch. Оно явно задекодировано в base64. Среди всего прочего получаем последний флаг:
Вместо заключения
Большое спасибо авторам за интересные задания — это было увлекательно! Мы же, в свою очередь, уже давненько подумываем сделать что-то подобное =)