NOOB2ROOT

CVE watch CVE-2021-44228 Known exploited

Log4Shell: JNDI lookup injection in Log4j 2

User-controlled strings reaching a Log4j 2 logging call trigger a JNDI lookup, giving remote code execution.

Severity
10.0
critical
Vendor
Apache
Disclosed
10 Dec 2021
Affected
Log4j 2.0-beta9 through 2.14.1

CVSS breakdown

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Analysis

Log4Shell scored a 10.0 and deserved it. A single string, in a log message, in the most widely deployed logging library in the Java ecosystem.

#The bug

Log4j 2 supports lookups — template substitutions inside log messages. ${java:version} in a logged string gets replaced with the JVM version at logging time. Convenient.

One of the supported lookups is JNDI, the Java Naming and Directory Interface. ${jndi:ldap://host/path} does not merely substitute a value: it performs a directory lookup against the named server, retrieves an object, and deserialises it into the running JVM.

So if any attacker-controlled string reaches a logging call — log.info(userInput) — Log4j parses it for lookups, finds the JNDI directive, connects out to the attacker's LDAP server, and loads whatever class it is handed.

That is remote code execution with a payload that fits in a User-Agent header.

#Why the blast radius was unprecedented

Three things compounded.

Logging is everywhere, by design. The exploitable sink is not a parser or a deserialiser you could inventory — it is the logging call, and applications log untrusted input deliberately. Usernames on failed logins. User-Agent strings. Search queries. HTTP headers. The 404 path. Every one of those is an exploit vector, and they exist precisely because logging them is good practice.

The payload survives transformation. A string logged by a front-end service gets forwarded to a log aggregator, which logs it again. Systems with no internet exposure and no attacker interaction were exploited because something upstream forwarded them a poisoned string. Nested and obfuscated forms (${${lower:j}ndi:...}) defeated naive WAF rules within hours.

S:C — scope changed. That flag in the CVSS vector is why this is a 10.0 rather than a 9.8. The vulnerable component and the impacted component are not the same thing. A logging library compromises the entire application and everything it can reach.

Then there is the supply-chain dimension. Log4j is a transitive dependency of an enormous fraction of enterprise Java. Most affected organisations could not answer "do we use Log4j" for days, because they did not use it — something they depended on did. The remediation bottleneck was inventory, not patching.

#The fix history is worth knowing

The initial guidance — set log4j2.formatMsgNoLookups=true — was incomplete, and 2.15.0 was itself found insufficient (CVE-2021-45046), followed by 2.17.0 for a DoS (CVE-2021-45105) and 2.17.1 (CVE-2021-44832). Anyone who patched once in December 2021 and considered it closed was wrong at least twice.

2.17.1 or later is the answer for Log4j 2.x. Versions 1.x are not vulnerable to this specific bug but are end-of-life and carry their own.

#Detection

  • Inventory first — transitively. mvn dependency:tree, gradle dependencies, or an SCA tool. Also scan filesystems for the class itself, since shaded and fat JARs hide the dependency from build tooling: look for JndiLookup.class inside JARs, not just for a log4j-core artifact.
  • Outbound egress is the highest-fidelity signal. Exploitation requires the JVM to connect out to an attacker-controlled LDAP/RMI endpoint. An application server making an unexpected outbound LDAP connection is close to conclusive. This detects exploitation regardless of payload obfuscation, which WAF pattern-matching does not.
  • Log search for ${jndi: and its obfuscated variants — useful but easily evaded, and by now mostly background scanner noise.
  • Canary tokens: several services let you generate a benign JNDI string to test your own stack safely.

#Fixing it

Upgrade to 2.17.1+. If you genuinely cannot, remove the class from the artifact:

zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

That is a real mitigation rather than a configuration flag, and it survives the fact that several config-based mitigations turned out to be bypassable.

The durable control is egress filtering. An application server that cannot make arbitrary outbound connections cannot complete this exploit chain, regardless of what lands in its logs. Most exploitation of this class dies at the second stage, and almost nobody restricts outbound traffic from application servers.

#What generalises

Features that turn data into code, enabled by default, in libraries nobody thinks of as security-relevant. JNDI lookups in log messages served a real if narrow purpose; the failure was making it default-on in a library whose entire job is to process untrusted strings.

When you assess a dependency, the question is not just "has it had CVEs" but "what does it do with the data I hand it, and what is switched on that I never asked for?"

References