Прим. перев.: Эта заметка была написана исследователем ИТ-безопасности из компании Aqua Security, специализирующейся на DevSecOps. Она является прекрасной иллюстрацией тех тонкостей в конфигурации Kubernetes, что важно всегда держать в голове, обслуживая кластеры в production. Конечно, если вы думаете про их безопасность…
Kubernetes состоит из множества компонентов, и иногда их комбинирование определенным образом приводит к неожиданным результатам. В этой статье я покажу, как pod, запущенный с привилегиями root'а и примонтированной директорией /var/log
узла, может раскрыть содержимое всей файловой системы хоста пользователю с доступом к его логам. Мы также обсудим варианты решения этой проблемы.
Как Kubernetes видит логи
Задумывались ли вы над тем, как kubectl logs <pod_name>
извлекает логи из pod'а? Кто отвечает за сбор логов из контейнеров? И как они попадают на ваш компьютер?
Следующая схема иллюстрирует процесс:
Kubelet создает структуру внутри директории /var/log
на хосте, представляющую pod'ы на узле. В директории для нашего pod'а есть файл 0.log
(1), но на самом деле это симлинк на лог контейнера, лежащий в /var/lib/docker/containers
. Это все с точки зрения хоста.
Kubelet открывает endpoint /logs/
(2), который просто работает с файловым HTTP-сервером в директории (3), делая логи доступными для запросов, поступающих от API-сервера.
Теперь представьте, что мы развернули pod с hostPath
, примонтированным в /var/log
. У такого pod'а будет доступ ко всем лог-файлам на хосте. Хотя уже это само по себе является потенциальной проблемой, мы можем сделать следующий логический шаг. Что, если заменить 0.log
на симлинк к… скажем, /etc/shadow
?
│
├── var
│ ├── logs
│ │ ├── pods
│ │ │ ├── default_mypod_e7869b14-abca-11e8-9888-42010a8e020e
│ │ │ │ ├── mypod
│ │ │ │ │ ├── 0.log -> /etc/shadow
│ │ │ │ │ │
Теперь, пытаясь загрузить логи с помощью kubectl logs
на клиентской машине, получим:
$ kubectl logs mypod
failed to get parse function: unsupported log format: "root:*:18033:0:99999:7:::n"
Kubelet переходит по ссылке и читает содержимое файла, на который она указывает (им может быть любой файл на узле).
Поскольку ожидался JSON, kubectl вылетел после первой строки, однако мы можем легко прочитать конкретные строки файла shadow
, запуская команду с флагом –-tail=-<line_number>
.
Это поразительно. Поскольку kubelet переходит по симлинку, можно воспользоваться его root-правами для чтения любого файла на узле, просто создавая символическую ссылку внутри pod'а.
Побег из pod'а
Пойдем еще дальше. Мы знаем, что при запуске pod'а в Kubernetes в него устанавливается токен ServiceAccount. Таким образом, если service account разрешает доступ к логам, мы можем напрямую получить доступ к kubelet'у и root-привилегии на узле.
Я написал proof of concept (POC), демонстрирующий данный вектор атаки:
- развертывание pod'а с точкой монтирования
/var/log
; - создание символической ссылки на корневую директорию хоста;
- чтение закрытого ключа ssh пользователя на хосте.
В следующем видео показаны две особые команды, выполняющиеся внутри pod'а:
-
lsh == ls
(на файловой системе хоста); -
cath == cat
(на файловой системе хоста).
(Прим. перев.: К сожалению, на хабре так и не починили вставку контента с asciinema, хотя мы уже обращались по данной проблеме, поэтому вынуждены «вставлять» видео простой ссылкой выше.)
Все файлы, задействованные в этом POC, можно найти в соответствующем репозитории GitHub. Там же лежит еще один POC-скрипт, который автоматически собирает закрытые ключи и токены ServiceAccount с файловой системы хоста.
Монтирование директорий может быть опасным
Итак, это уязвимость или просто плохая практика?
Развертывание pod'а с открытым для записи hostPath
в /var/log
встречается редко (кроме того, есть другие способы злоупотребить монтированием секретных директорий хоста в pod). Но даже если вы знали, что монтирование /var/log
— сомнительная практика, вы скорее всего не ожидали, что она позволит с такой легкостью завладеть узлом.
Перед публикацией мы обратились в команду Kubernetes по безопасности, чтобы узнать, считают ли они это уязвимостью. Они пришли к выводу, что это всего лишь печальное последствие монтирования закрытой директории хоста с правами записи: риски, связанные с этим, хорошо задокументированы. Впрочем, этой уязвимостью довольно легко воспользоваться. В мире есть множество проектов, которые пользуются данным монтированием. Если вы используете один из этих проектов, помните, что ваш deployment будет уязвим перед таким способом захвата хоста.
Этот метод был протестирован на Kubernetes 1.15 и 1.13, но скорее всего затрагивает и другие версии.
Устранение
Такой «побег» возможен только в том случае, если pod работает под root'ом. Вообще этого следует избегать. Aqua CSP позволяет с минимумом усилий задать политику, предотвращающую работу контейнеров под root'ом или выдающую разрешения только определенной группе образов, для которой действительно требуется root.
Другой способ — просто не развертывать pod'ы с hostPath
с правами записи в /var/log
. Этот подход не задается по умолчанию и не является обычной практикой, поэтому необходимо сознательно его определять (впрочем, возможность по-прежнему остается). Но как проверить?
Мы добавили новый скрипт (hunter) в kube-hunter — наш легковесный Open Source-инструмент для тестирования Kubernetes, — проверяющий кластер на предмет существования pod'ов с такими опасными точками монтирования. (Прим. перев.: Kube-hunter присутствовал в недавнем обзоре утилит для безопасности K8s, что мы публиковали в своем блоге.)
Пользователи Aqua могут защититься от данного риска, используя runtime-политику для запрета монтирования определенных томов:
Итог
Kubernetes — сложная система с массой тонкостей в настройке безопасности, которые не всегда очевидны для рядового и даже опытного пользователя. В этой статье я показал, как при определенных обстоятельствах невинное ведение логов может привести к потенциальной уязвимости. В большинстве случаев это невозможно, однако Kubernetes предлагает пользователям большую свободу действий, которые могут отразиться на безопасности. Важно помнить об этом и внедрять соответствующие средства контроля, позволяющие предотвратить подобные ошибки.
P.S. от переводчика
Читайте также в нашем блоге:
- «В 19% популярнейших Docker-образов нет пароля для root»;
- «33+ инструмента для безопасности Kubernetes»;
- «Введение в сетевые политики Kubernetes для специалистов по безопасности»;
- «Docker и Kubernetes в требовательных к безопасности окружениях»;
- «9 лучших практик по обеспечению безопасности в Kubernetes»;
- «11 способов (не) стать жертвой взлома в Kubernetes».
Автор: Андрей Сидоров