Что вы должны запретить в RBAC прямо сейчас, чтобы не потерять кластер
Вы думаете, что ваш RBAC защищает кластер, но есть пять прав, о которых администраторы обычно забывают. В статье-переводе мы покажем, какие это права и почему они опасны.
От переводчика: это перевод статьи Рори Маккьюна (Rory McCune) «Beyond the Surface». В ней разбирается один конкретный вектор атаки: как создать постоянный доступ к кластеру, минимизировав риск обнаружения, какие встроенные Kubernetes-механизмы для этого использовать и как после этого оставаться в системе. Для Kubernetes-администраторов это руководство по пониманию реальных угроз и слабых мест в RBAC. Дальше слово автору.
Цель статьи — описать один из векторов атаки, который злоумышленники могут использовать для сохранения и расширения своего доступа после первичной компрометации кластера Kubernetes, получив учётные данные администратора. Она не охватывает все возможные способы, но описывает один конкретный сценарий. Надеюсь, она также поможет понять некоторые внутренние механизмы и стандартные настройки, которые злоумышленники могут использовать в своих интересах.
Общий сценарий таков: злоумышленники получают временный доступ к ноутбуку администратора кластера, который отошёл, чтобы ответить на звонок, и не заблокировал устройство. Их задача — выяснить, как получить и сохранить доступ к кластеру до возвращения администратора.
Первоначальный доступ
Первое, что, скорее всего, захочет сделать хакер, — получить root-шелл на одном из узлов кластера. Это отличная отправная точка, чтобы поискать другие учётки или подложить свои бинарники. В Kubernetes это делается элементарно, потому что есть встроенная функция kubectl debug.
Типичная команда выглядит следующим образом (только подставьте имя из вашего кластера):
Важный момент здесь — флаг --profile, поскольку он определяет уровень доступа к узлу. Профиль sysadmin обеспечивает наивысший уровень доступа, поэтому он наиболее полезен для злоумышленников.
Запуск исполняемых файлов
Получив доступ к оболочке узла, злоумышленник, скорее всего, попытается загрузить и запустить свои инструменты. Это может оказаться не так просто, поскольку многие дистрибутивы Kubernetes защищают ОС узла, монтируя файловые системы в режиме «только для чтения» или с флагом noexec. Но есть одна вещь, которую умеют выполнять узлы любого Kubernets-кластера, — запуск контейнеров! Так что если атакующий сможет загрузить на узел контейнер и запустить его на нём, то у него будет возможность выполнить любую программу.
Чтобы понять, как это сделать, давайте рассмотрим некоторые малоизвестные фичи Kubernetes. В кластере все контейнеры запускаются средой исполнения контейнеров (container runtime), обычно это containerd или CRI-O. Находясь на узле, можно взаимодействовать с этими программами напрямую, в обход API Kubernetes.
В примере ниже я создаю новый неймспейс containerd с помощью утилиты ctr. Это полезно, потому что (по моему опыту):
- ctr всегда идёт в комплекте с containerd, не нужно качать никаких сторонних клиентов;
- контейнер в отдельном неймспейсе сложнее заметить тому, кто мониторит хост.
Важно: неймспейсы containerd — это не то же самое, что неймспейсы Kubernetes или Linux.
Назовём его sys_net_mon — это не так бросается в глаза, как что-нибудь вроде «здесь-были-хакеры». Когда неймспейс готов, нужно скачать образ контейнера:
Самое интересное, что внутри этого образа нет ничего, связанного с systemd или мониторингом сети. С точки зрения безопасности важно помнить: Docker Hub не следит за содержимым неофициальных и непроверенных (unverified) образов. Кроме того, назвать свой образ можно как угодно.
Теперь воспользуемся ctr, чтобы запустить контейнер:
Этот контейнер даёт нам полный доступ к файловой системе и сетевым интерфейсам хоста, что очень удобно для дальнейшего развития атаки. Теперь остаётся лишь запустить командную оболочку внутри этого контейнера:
Статические манифесты
Ещё один способ, которым можно запустить контейнер на узле, — это статические манифесты. На большинстве хостов у kubelet'а есть специальная директория, из которой он автоматически подгружает статические манифесты. Поды, которые описываются такими манифестами, запускаются вообще без участия API-сервера. И тут у атакующих есть классный трюк: прописать свой статический под в несуществующем неймспейсе. Тогда он не зарегистрируется на API-сервере и не будет показываться в kubectl get pods -A или других подобных командах. Подробнее о статических подах и их особенностях в плане безопасности можно почитать в блоге Иэна Смарта.
Удалённый доступ
Следующая проблема для наших атакующих — сохранить удалённый доступ к системе после возвращения администратора. Существует множество программ для удалённого доступа, но большинство хакерских утилит легко обнаруживаются EDR/XDR-агентами, поэтому в качестве альтернативы можно использовать что-то вроде Tailscale.
У Tailscale есть несколько фич, очень полезных для атакующих (вдобавок к их обычному применению!). Первая — его можно запустить с помощью всего двух статически скомпилированных Go-бинарников, которые легко переименовать. Другими словами, можно выбрать, что будет отображаться в списке процессов на узле. В том же духе, что и с образом контейнера, воспользуемся бинарниками с именами systemd_net_mon_server и systemd_net_mon_client.
Запуск сервера:
Запуск клиента:
Что касается сети, если используется DERP-сеть Tailscale, трафик пойдёт через порт 443/TCP, а такой доступ обычно разрешен в большинстве окружений. К тому же можно использовать Tailscale'овский ACL (Access Control List, список контроля доступа), чтобы запретить «подсадному» контейнеру связываться с другими машинами в сети Tailnet злоумышленника.
Как только эти сервисы поднимутся, можно будет спокойно заходить обратно в контейнер по SSH. У Tailscale свой SSH-сервер в комплекте, так что никакой «левый» sshd не будет светиться в процессах :).
Учётные данные — API kubelet'а
После получения удалённого доступа злоумышленникам всё ещё необходимы «долгоиграющие» учётные данные. Кроме того, было бы неплохо иметь возможность «прощупывать» кластер в обход API Kubernetes, поскольку такие действия могут засветиться в журналах аудита. Для этого им нужен доступ к учётным данным пользователя, который может напрямую обращаться к API kubelet'а. Этот API работает на каждом узле на порту 10250/TCP и лишён встроенных опций аудита.
В своём докладе для этой цели я использую инструмент teisteanas, который через API запросов на подпись сертификатов (Certificiate Signing Request, CSR API) создает учётные данные в kubeconfig-формате. Так можно завести учётку для любого пользователя. Для скрытности злоумышленник, скорее всего, выберет существующего пользователя, которому уже назначены права в системе RBAC, чтобы избежать необходимости создавать новые роли кластера или привязывать роли. Конкретный выбор пользователя зависит от окружения. В своих демках я использую kube-apiserver — пользователя, существующего в кластерах GKE.
С kubeconfig-файлом и доступом к порту kubelet'а на хосте можно получать списки подов на узле или выполнять команды внутри этих подов. Проще всего это сделать с помощью утилиты kubeletctl. Таким образом, из нашего контейнера, работающего в сетевом неймспейсе узла, можно выполнить следующую команду:
CSR API
Стоит также пару слов сказать о CSR API — для атакующих это очень удобная лазейка. Этот API встроен практически во все дистрибутивы Kubernetes. Через него можно создавать учётные данные для доступа к кластеру (кроме EKS, там это не работает). Самое важное: этими учётными данными может воспользоваться любой, у кого есть доступ к API-серверу. А поскольку большинство облачных Kubernetes-решений по умолчанию «выставляет» API-сервер в интернет, атакующий с такими учётными данными сможет подключиться к кластеру откуда угодно.
CSR API также привлекателен для злоумышленников по ряду других причин:
- Если ведение журналов аудита не включено и не настроено должным образом, не остаётся никаких записей ни об использовании этого API, ни о создании учётных данных.
- Учётные данные, созданные через этот API, не могут быть отозваны без ротации корневого сертификата всего кластера, что чревато. Соответствующее Issue на GitHub, касающееся отзыва сертификатов, открыто всего лишь с 2015 года, так что в ближайшей перспективе вряд ли что изменится.
- Можно создавать учётные данные для системных аккаунтов. Поэтому даже при включённом аудите бывает сложно отличить вредоносную активность от легитимной.
- Учётные данные, как правило, долгоживущие. Конечно, срок их действия зависит от конкретного дистрибутива, но обычно он варьируется в диапазоне 1–5 лет.
В примерах для доклада я использую кластер GKE и с помощью CSR API создаю учётку для пользователя system:gke-common-webhooks, у которого довольно много прав.
Token Request API
Даже если CSR API недоступен, в Kubernetes есть и другой встроенный способ заводить новые учётки — Token Request API. Обычно он используется для создания токенов для сервисных аккаунтов, но администратор с нужными правами может адаптировать его для своих задач. Как и в случае с CSR API, никаких следов не остаётся (кроме логов аудита), и новые учётки сложно отозвать, особенно если использовался сервисный аккаунт системного уровня, ведь единственный способ — удалить сам аккаунт, к которому привязан токен.
Со сроком жизни токена тоже не всё так плохо для хакера. В зависимости от дистрибутива он может варьироваться от 24 часов до целого года (по крайней мере в тех managed-дистрибутивах, что я видел).
В докладе я использую утилиту tocan, чтобы упростить создание kubeconfig-файла из токена сервисного аккаунта.
Выбранный сервисный аккаунт очень интересен тем, что у него есть право escalate. Это означает, что он всегда может стать cluster-admin'ом, даже если у него изначально нет таких прав. (Я уже писал об escalate ранее.)
Обнаружение подобных атак
Пора поговорить о том, как обнаруживать и предотвращать такие атаки. Чтобы их выявить, стоит обратить внимание на несколько ключевых моментов:
- Журналы аудита Kubernetes — крайне важный аспект. Необходимо, чтобы журналирование было включено, логи были централизованы и хранились достаточно долго. Это позволит выявить некоторые из описанных техник, особенно злоупотребление CSR API и Token Request API.
- Агенты на узлах — агенты безопасности на узлах кластера помогут обнаружить активность вроде трафика Tailscale (в зависимости от их настроек).
- Журналы узлов — важно обеспечить надлежащую централизацию и хранение журналов с узлов, поскольку злоумышленники могут оставлять в них следы.
- Знай свою систему — звучит просто, но это не так. Если вы знаете, какие процессы в норме работают на ваших узлах, то сможете заметить аномалии вроде systemd_net_mon. Проблема в том, что у каждого дистрибутива свой набор системных сервисов, которые запускает облачный провайдер, так что разобраться в том, что есть норма, — задача непростая.
Как защититься
Пара советов администраторам, как снизить риски такого сценария:
- Изолируйте свои кластеры от интернета! Публичный доступ к API-серверу означает, что вы находитесь в шаге от серьёзных проблем в случае утери учётных данных. Как правило, managed-дистрибутивы Kubernetes позволяют ограничить доступ, но по умолчанию такое ограничение не настроено.
- Принцип наименьших привилегий — в рассмотренном сценарии скомпрометированный ноутбук имел права уровня cluster-admin, что позволило злоумышленникам легко перемещаться по кластеру. Если бы администратор использовал учётную запись с меньшими привилегиями, атака, скорее всего, провалилась бы. Хотя некоторые из использованных прав, такие как отладка узлов, довольно распространены, другие (вроде доступа к CSR API и Token Request API) вряд ли необходимы для повседневного администрирования и могут быть отключены.
Заключение
В этой статье рассмотрен лишь один из возможных векторов, который злоумышленники могут использовать для сохранения и расширения доступа к кластеру. Очевидно, существуют и другие возможности. Надеюсь, этот материал прольёт свет на некоторые аспекты работы Kubernetes и поможет понять, как повысить безопасность кластера.