
Информация о Kubernetes CVE-2020-8558, включая эксплойт доказательства концепции.
CVE-2020-8558 — это уязвимость Kubernetes, опубликованная из-за того, что kube-proxy неожиданно делает службы хоста, привязанные к localhost, доступными другим узлам в сети. Я делаю акцент на "неожиданно", потому что эта уязвимость является следствием ошибки проектирования (недосмотра), а не ошибки реализации (бага). Код делает именно то, что заявлено, но мы все не смогли осознать последствия этого решения для безопасности.
Чтобы разрешить процессам хоста доступ к службам NodePort через адрес 127.0.0.1 (localhost), kube-proxy устанавливает sysctl параметр net.ipv4.conf.all.route_localnet=1. Согласно документации ядра, этот параметр заставляет ядро "не рассматривать адреса loopback как марсианские" — следствием чего является то, что они могут быть доступны другим узлам в сети. Это большая проблема, если у вас есть чувствительные службы без аутентификации, единственной защитой которых является привязка к localhost!
На момент написания сообщество Kubernetes всё ещё ищет наилучший способ решения CVE-2020-8558. Два очевидных варианта: перестать устанавливать sysctl route_localnet с самого начала или блокировать неправильно маршрутизированные пакеты localnet с помощью iptables. Исправление, использующее вторую стратегию, уже выпущено в kubelet >= 1.18.4, 1.17.7 или 1.16.11. Вы также можете применить его самостоятельно, обратившись к соответствующей проблеме Kubernetes для данной CVE, ссылка ниже.
Почему установка net.ipv4.conf.all.route_localnet=1 заслуживает идентификатора CVE? Прежде всего, потому что это нарушает нашу интуицию о IP-сетях.
По крайней мере с RFC 1122 от 1989 года пакеты из сети localhost 127.0.0.1/8 обрабатываются особым образом, им запрещено появляться "за пределами хоста". (Пожалуйста, свяжитесь со мной в Twitter, если вам известно о более раннем упоминании особых свойств 127/8.) Любой хост, соответствующий RFC, по сути имеет неявное, неудаляемое правило брандмауэра, блокирующее внешний доступ к службам, привязанным к 127.0.0.1 (и другим IP в этой сети — попробуйте пропинговать 127.127.127.127, если никогда этого не делали!). Мы привыкли полагаться на такое поведение и ожидать его. Мы часто запускаем чувствительные службы без аутентификации или шифрования и привязываем их к localhost для безопасности. Например, бэкенды HTTP в открытом виде, хранилище ключей-значений redis и пережиточный небезопасный порт Kubernetes api-server обычно защищены от вторжения таким образом. Мы настолько привыкли к этому поведению, что оно стало неотъемлемой частью нашего интуитивного понимания того, что значит быть IP-хостом. В таком свете понятно, почему многочисленные эксперты могли так долго упускать этот недостаток.
Как это работает?
Назовём любую сущность с IP-адресом "узлом". IP-пакеты отправляются от одного узла к другому, идентифицируясь по исходному и целевому IP-адресу в заголовке пакета. Каждый IP-узел является либо маршрутизатором (называемым шлюзом в RFC1122), либо хостом. Основное различие в том, что когда хост получает пакеты, предназначенные для чужого адреса, он игнорирует их. Маршрутизатор обращается к своей таблице маршрутизации и ретранслирует (пересылает) пакеты, пытаясь доставить их ближе к конечному адресату. Хост знает о некоторых локально подключённых узлах; для доступа к другим узлам он должен отправлять свои пакеты локально подключённому маршрутизатору. Эти локальные соединения могут быть типа «точка-точка» (например, PPP-соединение или некоторые виртуальные сети) или с общей средой (например, Ethernet).
Ваш почтовый ящик можно представить как соединение «точка-точка» между вашим домом и местным почтовым отделением. Чтобы маршрутизировать пакет по соединению «точка-точка», хосту нужно лишь указать правильный адрес назначения и передать пакет. (Это происходит на 3-м уровне модели OSI.) Чтобы маршрутизировать пакет по среде с общим доступом, хост должен сначала построить виртуальную цепь «точка-точка» через общую среду. В сетях Ethernet/IP это делается с помощью ARP на 2-м уровне модели OSI. По сути, если вы можете передать ARP-пакет, вы можете сказать другому хосту «Эй, я здесь», и он поверит вам. (Когда это делается неподобающим образом, это называется отравлением ARP-кэша.) Затем вы можете общаться, указывая соответствующие исходный и целевой Ethernet-адреса в своих пакетах.
Нормальный узел никогда не передаст пакет с адресом назначения 127.0.0.1 из-за RFC 1122. Если нормальный узел получает пакет с адресом назначения 127.0.0.1, он проигнорирует (отбросит) его, опять же из-за RFC 1122. Установка net.ipv4.conf.all.route_localnet=1 меняет это — она позволяет отправлять и получать пакеты с адресом 127.0.0.1, как если бы они не были особенными.
Итак, если злоумышленник имеет локальное соединение с целевым узлом, на котором установлен net.ipv4.conf.all.route_localnet=1, он может отправить ему пакет с адресом 127.0.0.1 в качестве адреса назначения, и целевой узел ответит соответствующим образом, как если бы 127.0.0.1 был совершенно обычным адресом. Два наиболее распространённых способа иметь локальное соединение с целевым узлом сегодня — находиться в той же Ethernet-сети (домене широковещания), что и цель, или быть контейнером, работающим на цели.
Обратите внимание, что при нормальной конфигурации Linux не позволит узлу злоумышленника передавать обычные пакеты, предназначенные для 127.0.0.1. Это можно обойти, перенастроив Linux-узел злоумышленника (если у них есть root-доступ) или подделав пакеты с помощью raw-сокета. Raw-сокеты требуют только возможности ядра Linux CAP_NET_RAW, которая по умолчанию предоставляется непривилегированным контейнерам. Это означает, что непривилегированный контейнер, контролируемый злоумышленником, способен эксплуатировать CVE-2020-8558.
Короче говоря, если вы используете kube-proxy или делаете хитрые вещи с net.ipv4.conf.*.route_localnet, вы подвержены риску. Вам следует потратить некоторое время на моделирование угроз, чтобы определить, насколько рискованна для вас эта подверженность, и спланировать соответствующую стратегию смягчения.
По сути, каждый Linux-хост с установленным net.ipv4.conf.all.route_localnet=1 уязвим. Заинтересует ли эта уязвимость злоумышленника, зависит от нескольких факторов:
Для оценки CVE-2020-8558 вы должны представить злоумышленников с различными возможностями и ответить на эти вопросы с их точки зрения. (Книга Адама Шостака «Моделирование угроз: проектирование для безопасности» описывает этот процесс очень подробно.) Два актуальных злоумышленника, которых вы обязательно должны рассмотреть: злоумышленник с узлом в вашей Ethernet-сети и злоумышленник, который может запускать код в непривилегированном pod на вашем хосте. Могут быть и другие интересные злоумышленники, которых вам также следует рассмотреть в зависимости от вашей среды и потребностей.
Для иллюстрации вот частично проработанный пример:
Хост, безусловно, доступен обоим злоумышленникам; мы это предположили в каждом случае.
Пакеты могут быть отфильтрованы, а могут и не быть. Вам нужно проверить. Во многих облачных средах и строго управляемых локальных сетях пакеты блокируются, если IP-адрес назначения не совпадает с ожидаемым сетью Ethernet-адресом. Само по себе это может стать концом игры для злоумышленника с узлом. Если на всех ваших узлах есть соответствующие локальные правила брандмауэра (например, предоставленные обновлённым kubelet), это приведёт к неудаче обоих злоумышленников.
Вероятно, интересных служб больше, чем вы думаете. Очевидно, что небезопасный порт Kubernetes api-server является очень привлекательной целью, и вам следует отключить его, если это возможно. Исследуйте все процессы, привязанные к IP-адресам в сети 127.0.0.0/8: есть ли у них надёжная аутентификация? Если нет, они могут быть раскрыты через CVE-2020-8558. Даже если все ваши обычные службы localhost безопасны, временные службы также могут вызывать беспокойство. Например, перенаправление портов SSH часто используется для обхода сетевых ограничений во временных, авторизованных целях. По умолчанию перенаправленные через SSH порты привязываются к localhost, чтобы временный доступ был разрешён только авторизованным пользователям. С CVE-2020-8558 эти «безопасные» переадресации портов также доступны вашим злоумышленникам.
Предполагая, что у вас есть root на Linux-машине в том же домене широковещания, что и цель, следующие настройки конфигурации позволят вам эксплуатировать CVE-2020-8558:
ip addr add 127.0.0.2/8 dev lo
ip addr del 127.0.0.1/8 dev lo
ip route add 127.0.0.1/32 via YOUR-TARGET-HERE
sysctl net.ipv4.conf.all.route_localnet=1
Поскольку некоторые важные службы (кхм, кхм, systemd-resolved) работают на адресе 127.0.0.0/8, мы добавляем новый, чтобы не нарушить работу хоста. Затем хост должен забыть о своём адресе по умолчанию 127.0.0.1/8. Далее мы инструктируем ядро маршрутизировать трафик для 127.0.0.1 по сети к вашей цели, которая знает, как получить доступ к 127.0.0.1. Наконец, мы устанавливаем пресловутый sysctl, который в противном случае заблокировал бы работу этой конфигурации.
Простой скрипт на Python для проверки CVE-2020-8558 путём отправки raw-пакетов. Это могло бы быть однострочником на scapy, но я хотел добавить немного больше удобств. Он отправляет пакет на 127.0.0.1 через вашу цель и проверяет, есть ли ответ.
Скрипт на Python для эксплуатации CVE-2020-8558, позволяющий обычным клиентским приложениям TCP или UDP взаимодействовать с удалённым IP-адресом localhost через поддельные пакеты. Запустите этот скрипт, затем используйте любой обычный TCP- или UDP-клиент (например, kubectl или nc), чтобы подключиться к вашему fakedestination (по умолчанию 198.51.100.1).
Обратите внимание, что fakedestination должен быть IP-адресом, который никогда не отвечает на пакеты, и ваш маршрут к нему должен проходить через тот же интерфейс, через который вы получаете доступ к вашей цели. В обычном случае и fakedestination, и цель будут доступны через интерфейс вашего шлюза по умолчанию, и это не будет большой проблемой.
Поскольку этот скрипт использует raw-сокеты для отправки и получения пакетов «localhost», он отлично работает внутри обычного непривилегированного контейнера.
Kubernetes issue for this CVE on GitHub
Kernel IP sysctl documentation
Adam Shostack: Threat Modeling
Особая благодарность Яну Колдуотеру, Брэду Гисаману, Даффи Кули и Лорану Бернайлю. Спасибо за мысли, советы и смех, всем вам. Гудик планете!