
Informações sobre o Kubernetes CVE-2020-8558, incluindo exploit de prova de conceito.
A CVE-2020-8558 é uma vulnerabilidade do Kubernetes que foi divulgada porque o kube-proxy inesperadamente disponibiliza serviços do host vinculados a localhost para outros na rede. Eu enfatizo o "inesperadamente" porque essa vulnerabilidade é decorrente de uma falha de design (descuido), não de uma falha de implementação (bug). O código faz exatamente o que diz, mas todos nós falhamos em reconhecer as implicações de segurança dessa decisão.
Para permitir que processos do host acessem serviços NodePort via endereço 127.0.0.1(localhost), o kube-proxy define a configuração sysctl net.ipv4.conf.all.route_localnet=1. De acordo com a documentação do kernel, essa configuração faz o kernel "não considerar endereços de loopback como marcianos" -- uma consequência disso é que eles poderiam ser acessados por outros nós da rede. Isso é um grande problema se você tiver serviços sensíveis não autenticados cuja única proteção é estarem vinculados ao localhost!
No momento em que este texto foi escrito, a comunidade Kubernetes ainda está trabalhando na melhor maneira de lidar com a CVE-2020-8558. As duas opções óbvias são parar de configurar o sysctl route_localnet em primeiro lugar, ou bloquear pacotes localnet com roteamento inadequado usando iptables. Uma correção usando a última estratégia já foi lançada no kubelet >= 1.18.4, 1.17.7 ou 1.16.11. Você também pode aplicá-la você mesmo consultando a issue do Kubernetes para esta CVE, linkada abaixo.
Por que definir net.ipv4.conf.all.route_localnet=1 merece um ID de CVE? Principalmente porque viola nossa intuição sobre redes IP.
Desde pelo menos o RFC 1122 de 1989, pacotes da rede localhost 127.0.0.1/8 são tratados de forma especial, sendo proibidos de aparecer "fora de um host". (Por favor, contate-me no Twitter se você souber de uma referência anterior às propriedades especiais de 127/8.) Qualquer host compatível com RFC essencialmente tem uma regra de firewall implícita e não removível que bloqueia acesso externo a serviços vinculados a 127.0.0.1 (e outros IPs nessa rede -- tente pingar 127.127.127.127 se nunca o fez antes!). Nós passamos a depender e esperar esse comportamento. Frequentemente executamos serviços sensíveis sem autenticação ou criptografia e os vinculamos ao localhost por segurança. Por exemplo, backends HTTP em texto puro, o armazenamento chave-valor redis e a porta insegura vestigial do api-server do Kubernetes são geralmente protegidos dessa forma contra intrusões. Estamos tão acostumados com esse comportamento que ele faz parte do nosso senso intuitivo do que significa ser um host IP. Visto sob essa ótica, é compreensível que inúmeros especialistas tenham ignorado essa falha por tanto tempo.
Como isso funciona?
Vamos chamar qualquer entidade com um endereço IP de "nó". Pacotes IP são enviados de um nó para outro, identificados pelo endereço IP de origem e destino no cabeçalho do pacote. Todo nó IP é um roteador (chamado de gateway no RFC1122) ou um host. A principal diferença é que quando um host recebe pacotes destinados ao endereço de outra pessoa, ele os ignora. Um roteador consulta sua tabela de roteamento e retransmite (encaminha) pacotes na tentativa de aproximá-los do destino final. Um host conhecerá alguns nós conectados localmente; para acessar outros nós, ele deve enviar seus pacotes a um roteador conectado localmente. Essas conexões locais podem ser ponto-a-ponto (como um link PPP ou algumas redes virtuais) ou de meio compartilhado (como Ethernet).
Sua caixa de correio pode ser conceituada como um link ponto-a-ponto entre sua casa e a agência de correio local. Para rotear um pacote por um link ponto-a-ponto, o host só precisa aplicar o endereço de destino correto e transmitir o pacote. (Isso ocorre na Camada 3 do modelo OSI.) Para rotear um pacote por um link de meio compartilhado, o host deve primeiro construir um circuito ponto-a-ponto virtual através do meio compartilhado. Em redes Ethernet/IP, isso é feito por ARP, na camada 2 do modelo OSI. Essencialmente, se você consegue transmitir um pacote ARP, você pode dizer a outro host "Ei, eu estou aqui" e ele acreditará em você. (Quando isso é feito de forma inadequada, é chamado de envenenamento de cache ARP.) Então você pode se comunicar colocando os endereços Ethernet de origem e destino apropriados em seus pacotes.
Um nó normal nunca transmitirá um pacote com endereço de destino 127.0.0.1, por causa do RFC 1122. Se um nó normal recebe um pacote com endereço de destino 127.0.0.1, ele o ignorará (descartará), novamente por causa do RFC 1122. Definir net.ipv4.conf.all.route_localnet=1 muda isso -- permite que pacotes 127.0.0.1 sejam enviados e recebidos como se não fossem especiais.
Então, se um atacante tem uma conexão local com um nó alvo com net.ipv4.conf.all.route_localnet=1, o atacante pode enviar-lhe um pacote com 127.0.0.1 como endereço de destino, e esse nó alvo responderá adequadamente como se 127.0.0.1 fosse um endereço totalmente normal. As duas formas mais comuns de ter uma conexão local com um nó alvo hoje são estar na mesma rede Ethernet (domínio de broadcast) que o alvo, ou ser um contêiner executando no alvo.
Observe que, quando configurado normalmente, o Linux não permitirá que o nó atacante transmita pacotes normais destinados a 127.0.0.1. Isso pode ser contornado reconfigurando o nó Linux do atacante (se ele tiver acesso root) ou forjando pacotes usando um soquete bruto. Soquetes brutos exigem apenas a capacidade CAP_NET_RAW do kernel Linux, que é dada por padrão a contêineres sem privilégios. Isso significa que um contêiner sem privilégios controlado por um atacante é capaz de explorar a CVE-2020-8558.
Em resumo, se você está usando kube-proxy ou fazendo coisas engenhosas com net.ipv4.conf.*.route_localnet, você está exposto. Você deve gastar algum tempo com modelagem de ameaças para determinar quão arriscada essa exposição é para você e planejar uma estratégia de mitigação adequada.
Fundamentalmente, todo host Linux com net.ipv4.conf.all.route_localnet=1 definido é vulnerável. Se essa vulnerabilidade é interessante para um atacante depende de vários fatores:
Para avaliar a CVE-2020-8558, você deve imaginar atacantes com várias capacidades e responder a essas perguntas do ponto de vista desses atacantes. (O livro de Adam Shostack, "Threat Modeling: Designing for Security", descreve esse processo em grande detalhe.) Dois atacantes relevantes que você certamente deve considerar são um atacante com um nó na sua rede Ethernet e um atacante que pode executar código em um pod sem privilégios no seu host. Pode haver outros atacantes interessantes que você também deve considerar, dependendo do seu ambiente e necessidades.
Para ilustrar, aqui está um exemplo parcialmente trabalhado:
O host certamente está acessível a ambos os atacantes; nós assumimos isso em cada caso.
Os pacotes podem ou não ser filtrados. Você precisará verificar. Em muitos ambientes de nuvem e redes on-prem estritamente gerenciadas, pacotes são bloqueados se o destino IP não corresponder ao destino Ethernet esperado pela rede. Isso por si só poderia ser o fim do jogo para o atacante-com-um-nó. Se todos os seus nós têm regras de firewall locais apropriadas (como as fornecidas por um kubelet atualizado), isso fará com que ambos os atacantes falhem.
Provavelmente há mais serviços interessantes do que você pensa. Obviamente, a porta insegura do api-server do Kubernetes é um alvo altamente convidativo, e você deve desativá-la se puder. Investigue todos os processos vinculados a endereços IP na rede 127.0.0.0/8: eles têm autenticação robusta? Se não, eles podem ser expostos via CVE-2020-8558. Mesmo que todos os seus serviços normais de localhost sejam seguros, os efêmeros também podem ser uma preocupação. Por exemplo, o encaminhamento de porta SSH é frequentemente usado para contornar restrições de rede para fins temporários e autorizados. Por padrão, portas encaminhadas via SSH são vinculadas ao localhost para que o acesso temporário seja permitido apenas a usuários autorizados. Com a CVE-2020-8558, esses encaminhamentos de porta "seguros" também estão disponíveis para seus atacantes.
Supondo que você tenha root em uma máquina Linux no mesmo domínio de broadcast que o alvo, as seguintes definições de configuração permitirão que você explore a 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
Como alguns serviços importantes (cof, cof, systemd-resolved) são executados em um endereço 127.0.0.0/8, adicionamos um novo para evitar quebrar o host. Então, o host deve esquecer seu endereço padrão 127.0.0.1/8. Em seguida, instruímos o kernel a rotear o tráfego para 127.0.0.1 pela rede até seu alvo, que sabe como acessar 127.0.0.1. Finalmente, definimos o infame sysctl que, de outra forma, bloquearia esta configuração de funcionar.
Simples script Python para testar a CVE-2020-8558 enviando pacotes brutos. Isso poderia ser um one-liner de scapy, mas eu queria adicionar um pouco mais do conforto de casa. Ele envia um pacote para 127.0.0.1 via seu alvo e verifica se há uma resposta.
Script Python para explorar a CVE-2020-8558 permitindo que aplicações clientes TCP ou UDP comuns se comuniquem com um IP localhost remoto via pacotes forjados. Execute este script e, em seguida, use qualquer cliente TCP ou UDP normal (ex.: kubectl ou nc) para conectar-se ao seu fakedestination (198.51.100.1 por padrão).
Observe que o fakedestination precisa ser um endereço IP que nunca responda a pacotes, e sua rota até ele deve ser através da mesma interface pela qual você acessa seu alvo. No caso usual, tanto o fakedestination quanto o alvo serão acessíveis via sua interface de gateway padrão, e isso não será um grande problema.
Como este script usa soquetes brutos para enviar e receber os pacotes "localhost", ele funciona bem dentro de um contêiner normal sem privilégios.
Issue do Kubernetes para esta CVE no GitHub
Documentação de sysctl IP do kernel
Adam Shostack: Threat Modeling
Um salve para Ian Coldwater, Brad Geesaman, Duffie Cooley e Laurent Bernaille. Obrigado pelos pensamentos, conselhos e risadas, pessoal. Honk the planet!