Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи?

Написать
Войти
Дайджесты
Схема обхода авторизации Kubelet через nodes/proxy GET

Критическая уязвимость Kubelet: обход авторизации через nodes/proxy

Анализ вектора атаки в Kubernetes через права nodes/proxy GET. Некорректная авторизация WebSocket-соединений позволяет выполнять произвольный код в любых подах напрямую через API Kubelet. Читайте детальный разбор уязвимости, список затронутых Helm-чартов и пошаговый чек-лист для аудита прав и защиты порта 10250.

Критическая уязвимость Kubelet: обход авторизации через nodes/proxy

Введение в архитектуру доступа: риски разрешений в Kubernetes

Управление доступом в Kubernetes опирается на ролевую модель (Role-Based Access Control, RBAC), сопоставляющую субъектов (пользователей или сервисные аккаунты ServiceAccounts) с разрешенными действиями. Каждое разрешение в RBAC задает группу API, тип ресурса и действие-глагол (verb): например, get, list или create.

В практике администрирования принято считать глаголы чтения (get и list) безопасными. Напротив, глаголы записи (такие как create, update или delete), особенно для выполнения команд внутри контейнеров (pods/exec), жестко ограничиваются.

Однако в этой модели существует серьезный архитектурный нюанс, связанный с ресурсом nodes/proxy. Доступ к nodes/proxy с глаголом get обычно рассматривается как безопасное разрешение «только для чтения». Этот доступ массово выдается агентам мониторинга и логирования (Prometheus, Grafana Promtail, Datadog), чтобы они могли опрашивать интерфейсы узлов кластера, читать системную статистику и собирать логи контейнеров. Но, как показывает исследование Graham Helton, сочетание этого разрешения с открытым сетевым доступом к агенту узла (Kubelet) ведет к удаленному выполнению кода (Remote Code Execution, RCE) в любых подах кластера напрямую через Kubelet API, полностью обходя изоляцию RBAC.

Механизм уязвимости: WebSocket-рукопожатие и авторизация Kubelet

Kubelet — это системный агент, работающий на каждом узле (ноде) кластера. Он управляет контейнерами и предоставляет программный интерфейс (Kubelet API) на защищенном порту 10250.

Когда администратор запускает сессию в контейнере (например, kubectl exec), запрос идет через центральный шлюз (API Server). Шлюз аутентифицирует пользователя, проверяет его права на создание подресурса pods/exec и перенаправляет запрос к Kubelet API. Поскольку выполнение команд требует интерактивного обмена данными, соединение переходит с HTTP на протокол WebSocket. Этот переход осуществляется отправкой HTTP-запроса GET с заголовками обновления соединения (Upgrade: websocket).

Суть уязвимости кроется в том, как Kubelet обрабатывает авторизацию для WebSocket-соединений напрямую, минуя API Server. Kubelet принимает решение о допуске на основе первоначального HTTP-запроса GET с заголовком Upgrade. Он проверяет, имеет ли субъект право выполнять GET на ресурс nodes/proxy. Если такое право есть, Kubelet одобряет установление WebSocket-соединения.

После завершения рукопожатия (handshake) и переключения канала в режим WebSocket Kubelet перестает контролировать соответствие действий исходным правам. Клиент может отправлять внутри установленного соединения любые управляющие фреймы, включая запросы на выполнение команд /exec в контейнерах. Kubelet ожидает, что выполнение команд требует операции уровня CREATE, но WebSocket-соединение уже установлено в рамках одобренного GET-запроса, и дополнительная проверка прав на этапе передачи команд не производится.

Таким образом, любой субъект, обладающий правами nodes/proxy GET и способный установить сетевое соединение с портом 10250 Kubelet на целевом узле, может выполнить произвольный код в любом контейнере на этом узле. При этом атакующему не требуются права на pods/exec или доступ к API Server.

Использование websocat для демонстрации вектора атаки

Для проверки инфраструктуры на наличие данной уязвимости исследователи используют утилиту websocat — консольный клиент для работы с WebSocket. Сценарий проверки выглядит так: атакующий извлекает токен авторизации (bearer token) сервисного аккаунта, которому выдана роль nodes/proxy GET. Имея сетевой доступ к порту 10250 Kubelet на узле, он отправляет запрос:

websocat -k -H "Authorization: Bearer <TOKEN>" \
  --protocol v4.channel.k8s.io \
  "wss://<NODE_IP>:10250/exec/<NAMESPACE>/<POD_NAME>/<CONTAINER_NAME>?output=1&error=1&command=id"

Параметр --protocol v4.channel.k8s.io указывает Kubelet использовать внутренний протокол обмена данными Kubernetes. После отправки такого запроса Kubelet одобряет подключение на основании того, что токен дает право на nodes/proxy GET. WebSocket-соединение открывается, команда id выполняется внутри контейнера, а результат возвращается клиенту.

Этот вектор не является классической ошибкой переполнения буфера или уязвимостью в коде. Разработчики Kubernetes классифицировали данное поведение как ожидаемое (intended behavior) и закрыли отчет со статусом «won't fix». Kubelet проектировался в предположении, что его порт 10250 полностью изолирован от недоверенного трафика и доступен только для API Server. Следовательно, ответственность за защиту этой плоскости целиком возлагается на администраторов кластеров, которые должны настраивать сетевые политики.

Масштаб проблемы и скрытность атаки

Проблема усугубляется тем, что выдача роли с правами nodes/proxy GET является распространенным паттерном для инфраструктурного ПО. Анализ Helm-чартов показал, что как минимум 69 известных проектов запрашивают это разрешение. В их числе популярные системы мониторинга и логирования: prometheus-community/prometheus, grafana/promtail, datadog/datadog, elastic/elastic-agent, а также сетевые плагины и средства безопасности cilium/cilium, opentelemetry-kube-stack, trivy-operator, newrelic-infrastructure и wiz-sec/sensor.

Эксплуатация зависит от включенных опций чарта и сетевой доступности порта 10250 Kubelet изнутри подов. Однако при компрометации токена агента атакующий получает готовый ключ для захвата узлов кластера.

При этом прямые запросы к Kubelet API идут в обход API Server. В журналах API Server отразятся лишь фоновые запросы SubjectAccessReview без информации о запущенной команде, что делает атаку крайне скрытной.

Пошаговый чек-лист для аудита прав и сетевой безопасности

Чтобы убедиться, что ваш кластер защищен, выполните аудит по следующему алгоритму:

  1. Инвентаризация ролей RBAC. Найдите все ClusterRole и Role, которые содержат разрешение на работу с ресурсом nodes/proxy с использованием глагола get:
    kubectl get clusterrole,role -A -o yaml | grep -B 5 -A 5 "nodes/proxy"
    
  2. Анализ привязок ролей (RoleBindings). Определите, к каким субъектам привязаны найденные роли. Выполните поиск по имени роли:
    kubectl get clusterrolebinding,rolebinding -A -o json | jq '.items[] | select(.roleRef.name == "<ROLE_NAME>") | {namespace: .metadata.namespace, name: .metadata.name, subjects: .subjects}'
    
  3. Проверка автоматического монтирования токенов. Если для скомпрометированного ServiceAccount опция automountServiceAccountToken установлена в true, токен в файловой системе контейнера (по пути /var/run/secrets/kubernetes.io/serviceaccount/token) может быть похищен злоумышленником.
  4. Сетевой smoke-тест доступности Kubelet API. Проверьте, есть ли сетевой путь из подов до порта 10250 любого узла кластера:
    curl -k -I https://<NODE_IP>:10250/metrics
    
    Если запрос возвращает HTTP-ответ, сетевой путь открыт и Kubelet API доступен для атаки. Если запрос завершается по таймауту, доступ заблокирован сетевыми политиками.

Рекомендации по устранению рисков и защите кластера

Полная защита инфраструктуры требует сочетания ограничения прав RBAC с ужесточением сетевой изоляции.

  • Минимизация привилегий (Least Privilege). Проанализируйте, требуется ли агентам мониторинга доступ к nodes/proxy. Часто современные версии Helm-чартов позволяют заменить это разрешение на более узкие эндпоинты, такие как nodes/metrics (для сбора метрик) или использование выделенных метрических API (Metrics API). Проверить официальную документацию Helm-чартов и переопределить значения (values), отключив функции, требующие работы с прокси узла, если они не критичны.
  • Ограничение сетевого доступа (Network Isolation). Сетевой порт 10250 Kubelet не должен быть доступен из обычных подов. Настройте сетевые политики Kubernetes (NetworkPolicy) для ограничения исходящего трафика (egress) из пространств имен приложений. Запретите подам отправлять пакеты на IP-адреса узлов кластера по порту 10250, разрешив этот доступ только для ограниченного списка системных компонентов.
  • Отключение анонимного доступа. Убедитесь, что на уровне конфигурации запуска Kubelet (файл /var/lib/kubelet/config.yaml на узлах) отключен анонимный доступ к API. Параметр authentication.anonymous.enabled должен быть установлен в false. Также включите обязательную авторизацию Webhook-запросов, установив параметр authorization.mode в Webhook.
  • Политики безопасности как код (Policy as Code). Внедрите проверку манифестов с помощью Admission Controllers (Kyverno или OPA Gatekeeper). Запретите создание ClusterRole и Role с ресурсом nodes/proxy и глаголом get без ручного одобрения безопасности.