Среда оркестрации контейнеризированных приложений Kubernetes получила в последние годы широкое распространение. Для этого есть множество причин.

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

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

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

Таким образом, совершенно очевидно, что Kubernetes является по сути основой для всей инфраструктуры, на которой работает приложение, и то, насколько безопасно оно будет работать во многом зависит от того, насколько правильно будет настроена сама среда оркестрации.

Конечно, можно внедрить различные механизмы защиты, например ролевую модель доступа, о которой мы уже говорили ранее, но судить о том, насколько хорошо защищена та или иная система можно только попытавшись пойти по пути хакера, то есть попытаться ее взломать. В реальной жизни эту задачу обычно выполняют так называемые «белые хакеры» — пентестеры. В этой статье мы попробуем посмотреть на безопасность Kubernetes как раз глазами пентестеров.

Сначала разведка

По традиции, злоумышленники обычно начинают поиск информации из открытых источников. И Kubernetes в этом плане не является исключением. Так, зная какие домены принадлежат данной организации и сервисы каких облачных провайдеров она использует, можно попробовать поискать нужные поддомены с помощью запросов вида Identity LIKE "k8s.%.com" на специализированных ресурсах типа crt.sh, где помимо информации об используемых сертификатах будут также указаны поддомены, связанные с Kubernetes.

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

Разработчики (как впрочем и другие ИТ-шники) в основной своей массе довольно ленивы и если кто-то за них сделал хотя бы часть работы, то они этим с удовольствием воспользуются. Поэтому, если атакующему известно какие сервисы и компоненты используются для работы приложения, то он может попробовать поискать на github файлы YAML для этих сервисов. 

Давайте рассмотрим небольшой пример. Допустим, из-за некорректной настройки веб сервера нам доступен файл phpinfo.php, из которого мы узнали, что целевое приложе��ие крутится на Nginx и использует PHP7. Теперь можно пойти на github и с помощью запросов вида «k8s nginx php 7» попробовать найти готовый набор YAML файлов, описывающий создание и развертывание всех необходимых для работы данных сервисов сущностей.

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

Помним про порты

Еще один древний, но до сих пор рабочий способ — это сканирование сетевых портов. Конечно, рассчитывать, что все из перечисленных ниже портов будут смотреть в интернет не стоит, хотя Shodan вполне может найти что-то открытое в сети. Однако, если у нас уже есть доступ во внутреннюю сеть, которая не слишком хорошо сегментирована, то сканирование портов вполне может принести результат. 

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

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

6443 (TCP) (Kubernetes API server), 2379-2380 (TCP) (etcd server client API kube-apiserver, etcd), 10250 (TCP) (Kubelet API Self, Control plane), 10259 (TCP) (kube-scheduler), 10257 (TCP) (kube-controller-manager). 

На рабочих узлах будут открыты следующие порты: 

10250 (TCP) (Kubelet API), 10256 (TCP) (kube-proxy, Load balancers), 30000-32767 (TCP) (NodePort Services).

Также имеется ряд вспомогательных сервисов, предназначенных для мониторинга состояния компонентов k8s, поэтому в примере ниже мы будем опрашивать несколько больше портов. Например, на всякий случай проверим небезопасный порт 8080. 

Для того, чтобы быстро поискать в сети только открытые порты k8s и вообще не создавать в трафике слишком много ненужного шума, просканируем сеть с помощью Nmap, указав только нужные порты

nmap -p 443,2379,4194,6443,8443,8080,10250,10255,10256,9099,6782-6784,44134
<pod_ipaddress>/16

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

Для узла, представленного на рисунке, такие проверки будут иметь следующий вид:

curl -k https://192.168.49.2:8443/api/v1

Итак, мы провели разведку и смогли обнаружить узлы Kubernetes. Теперь нам необходимо попытаться найти и проэксплуатировать уязвимости в настройках и компонентах среды оркестрации.

В этой статье мы не будем подробно рассматривать архитектуру Kubernetes, так как этой теме посвящено уже достаточно публикаций. Так что, мы сразу перейдем к поиску уязвимостей в компонентах k8s. 

Kubelet API

Служба kubelet работает на каждом узле кластера. C ее помощью осуществляется управление подами внутри узла. Kubelet API взаимодействует с основным модулем kube-apiserver. При этом важным моментом является то, что по умолчанию HTTP-запросы, которые не прошли аутентификацию, но не были отклонены, обрабатываются как анонимный доступ и считаются пользователями system:anonymous в группе system:unauthenticated. Соответственно, если вы обнаружите, что эта служба открыта, то возможно, вам удастся выполнить произвольный код.

Попробуем найти доступные ресурсы с помощью curl. Например, можно посмотреть метрики:

curl -k https://192.168.49.2:10250/metrics

Или рабочие поды:

curl -k https://192.168.49.2:10250/pods

Если в ответ мы получим «Unauthorized», то нам не повезло и требуется аутентификация. А вот если в ответ мы получили какой-нибудь JSON, то можно попробовать сделать что-нибудь интересное.

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

curl -sk https://192.168.49.2:10250/runningpods/

Далее можно попытаться выполнить в контейнерах нужные команды. Так, получив список подов, работающих на узле мы можем найти что-то интересное, например СУБД. Попробуем прочитать содержимое каталога:

$ curl -k -XPOST "https://192.168.49.2:10250/run/default/mysql-epg0f/mysql" -d
"cmd=ls -la /"

Пароль от СУБД можно поискать с помощью переменных окружения ОС:

$ curl -k -XPOST "curl -sk 

https://192.168.49.2:10250/run/default/mysql-epg0f/mysql" -d 'cmd=env'

Для специалистов по анализу кода могут оказаться интересными исходники Kubelet, которые можно взять по адресу:

curl -s https://raw.githubusercontent.com/kubernetes/kubernetes/master/pkg/kubelet/server/server.go

Итак, основной вектор атаки мы обозначили. Однако, что делать если у нас десятки узлов и сотни различных подов. В таком случае можно прибегнуть к небольшому скрипту, который позволит нам составить запросы curl на основе данных, полученных с помощью kubectl:

kubectl get nodes -o custom-columns='IP:.status.addresses[0].address,KUBELET_PORT:.status.daemonEndpoints.kubeletEndpoint.Port' | grep -v KUBELET_PORT | 
while IFS='' read -r node; do
    hst=$(echo $node | awk '{print $1}')
    prt=$(echo $node | awk '{print $2}')
    echo "curl -k --max-time 30 https://$hst:$prt/pods"
done

Как видно, здесь мы с помощью kubectl формируем запрос, для подключения к kubelet API.

Etcd API

Напомним, что компонент etcd это база данных для Kubernetes, являющаяся критически важным элементом любого кластера. Он хранит в себе всю информацию, нужную для его стабильной работы и соответственно, будет не очень хорошо, если злоумышленник получит к нему доступ.  

При наличии доступа по порту 2379 можно также обратиться к базе etcd с помощью curl:

url -k https://192.168.49.2:10250/version

etcdctl --endpoints=https://192.168.49.2:2379 get / --prefix --keys-only

Для автоматизации поиска etcd можно немного модифицировать пример из предыдущего раздела для обращений к порту 2379:

kubectl get nodes -o custom-columns='IP:.status.addresses[0].address,KUBELET_PORT:.status.daemonEndpoints.kubeletEndpoint.Port' | grep -v KUBELET_PORT | 
while IFS='' read -r node; do
    hst=$(echo $node | awk '{print $1}')
    echo "curl -k --max-time 30 https://$hst:2379/version" 
done

Таким образом, мы можем обнаружить узлы с доступным сервисом etcd. 

Helm и другие

До этого мы говорили об обнаружении и использовании компонентов самого k8s. Теперь рассмотрим выявление и использование такого интересного дополнительного элемента как сервис Helm.

Helm это популярный диспетчер пакетов для K8s, который значительно упрощает процесс установки, управления и масштабирования приложений в кластере. Устаревшие версии Helm используют сервис Tiller, который живет на порту 44134 (TCP). Помним, что устаревший софт, это находка для хакера, поэтому, если нам удалось обнаружить открытый порт, то можно попробовать к нему обратиться с помощью клиента Helm. В примере ниже мы узнаем версию Helm:

helm --host 192.168.49.2:44134 version

Еще одним интересным сервисом является cAdvisor. Это инструмент с открытым исходным кодом, предназначенный для мониторинга состояния контейнеров. Он используется для чтения характеристик производительности и использования ресурсов контейнеров, работающих в кластере. Данный сервис работает на порту 4194 TCP и к открытому порту можно обратиться с помощью curl:

curl -k https://192.168.49.2:4194

Сервис NodePort

Сервис NodePort мы уже упоминали, когда говорили о портах, открытых на рабочих нодах кластера. Через NodePort, один и тот же порт открывается во всех узлах, передающих трафик в сервис. По умолчанию этот порт будет находиться в диапазоне 30000–32767. Таким образом, через эти порты могут быть доступны новые непроверенные службы.

Можно отдельно поискать эти службы с помощью nmap:

sudo nmap -sS -p 30000-32767 192.168.49.2

Подводим итоги

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