In December 2021, a vulnerability in a small, ubiquitous Java logging library brought security teams across the world to a standstill. It became known as Log4Shell, formally tracked as CVE-2021-44228, and it carried the maximum possible CVSS base score of 10.0. This article is a defender's introduction: what the flaw actually is, which versions are affected, how it worked at a conceptual level, and what your blue team should do about it. It deliberately avoids any attack instructions and focuses entirely on understanding and defence.

If you want to check your own systems immediately, you can run the Log4Shell scanner while you read.

Why Log4Shell Mattered So Much

Apache Log4j is one of the most widely deployed logging frameworks in the Java ecosystem. It is embedded in enterprise applications, web servers, network appliances, cloud services, and countless products that ship Java under the hood. Because logging is everywhere, the vulnerable code was everywhere too. A single crafted string, sent in something as innocent as a username, a User-Agent header, or a chat message, could be enough to trigger remote code execution on a server that merely logged it.

That combination — trivial to trigger, enormous installed base, and full remote code execution — is what made Log4Shell one of the most serious vulnerabilities in recent memory. It did not require authentication, it did not require special access, and the malicious data could ride in on ordinary application traffic. That asymmetry is what alarmed defenders most: an attacker needed only to get a short string logged somewhere, while the defender had to find and fix every vulnerable copy across an entire estate. The effort was wildly lopsided in the attacker's favour, which is why the response demanded such urgency and coordination.

Which Log4j Versions Are Affected

CVE-2021-44228 affects Apache Log4j 2 from version 2.0-beta9 up to and including 2.14.1. The original Log4j 1.x line is a separate, end-of-life project and is not vulnerable to this specific JNDI lookup flaw, though it carries its own unrelated issues and should not be relied upon. If you are unsure which release you are running, the Log4j version checker maps a version number to its known vulnerabilities, and our guide on whether your Log4j version is vulnerable walks through how to read the result.

How the JNDI Lookup Worked, Conceptually

Log4j supported a feature called message lookups. When it logged a string, it would scan for special tokens written as ${...} and substitute them with dynamic values. For example, a token could expand an environment variable or a system property directly inside a log line. This was intended as a convenience.

The problem was that one of the supported lookup mechanisms was JNDI, the Java Naming and Directory Interface. JNDI can resolve names against remote directory services such as LDAP. When Log4j encountered a JNDI lookup token in a string it was logging, older versions would actually reach out to the named service and, under the default configuration, could load and execute a remote Java object. In other words, untrusted text that happened to be logged could cause the server to fetch and run code from an attacker-controlled location.

The key insight for defenders is that the dangerous part was not logging itself but the automatic, recursive expansion of lookup tokens inside attacker-influenced data, combined with JNDI's ability to load remote objects. We deliberately do not provide a working exploit chain here; understanding the concept is enough to defend effectively. For a deeper conceptual treatment, see how the JNDI lookup RCE worked.

What a Malicious String Looks Like

Defenders need to recognise the pattern so they can search for it in logs and traffic. The hostile tokens are built around the JNDI lookup syntax and frequently appear inside otherwise normal fields. Attackers quickly began obfuscating them so that simple keyword filters would miss them. A few illustrative shapes you might see in your logs:

  • Plain form: a JNDI lookup token referencing an ldap or rmi scheme inside a ${...} wrapper.
  • Case tricks: nested lookups such as ${lower:j} used to rebuild the letters of jndi one character at a time.
  • Default-value tricks: constructs like ${::-j} that emit a literal character through Log4j's default-value syntax.
  • Environment indirection: tokens that pull pieces of the string from environment variables to dodge static signatures.

These are detection patterns, not instructions. Their purpose here is so your team can build effective searches. Our article on obfuscated JNDI payloads breaks the evasion techniques down in detail.

The Follow-On CVEs You Should Know

Log4Shell was not a single patch story. The initial fix in 2.15.0 was found to be incomplete, leading to CVE-2021-45046, and a further denial-of-service issue was addressed as CVE-2021-45105. The recommended fixed line settled on 2.17.1, with backports 2.12.4 for Java 7 and 2.3.2 for Java 6. The difference between these CVEs matters when you are deciding how urgently to act; see CVE-2021-45046 vs 45105 for the distinction.

First Steps for Defenders

If you are responding now, work through a clear sequence rather than panicking:

  1. Inventory. Find every application and appliance that may bundle Log4j 2, including transitive dependencies and vendor products.
  2. Scan. Use the Log4Shell scanner to check exposed services and confirm where the vulnerable behaviour is present.
  3. Confirm versions. For each finding, identify the exact Log4j version with the version checker.
  4. Hunt. Search historical logs for the lookup patterns described above to see if exploitation was attempted before you patched.
  5. Remediate. Upgrade to 2.17.1 or the appropriate backport; our patching guide covers the details.

Why You Cannot Just Block One String

A common early reaction was to add a web application firewall rule blocking the literal text of the JNDI token. Because of the obfuscation techniques, those naive rules were bypassed almost immediately. Blocking is a useful speed bump and buys time, but it is not a fix. The only reliable remediation is to remove the vulnerable code path, either by upgrading or by the stopgap measures described in our guide on mitigation without upgrading.

Detecting Past Exploitation

Patching protects you going forward, but it does not tell you whether you were already hit. Because the malicious data flowed through normal logging, evidence often sits in your existing access logs, application logs, and outbound connection records. The practical upside is that you usually do not need exotic tooling to investigate; the data you already collect is often enough. The challenge is knowing what to look for and correlating a suspicious log line with the network activity that would confirm a successful attempt rather than a harmless probe. A sudden outbound connection from an application server to an unfamiliar host shortly after a suspicious log entry is a classic indicator. Our dedicated article on detecting Log4Shell in logs explains exactly what to grep for and how to reduce false positives.

Why the Lesson Outlasts the Bug

Even after every vulnerable Log4j instance is patched, Log4Shell remains instructive because its root cause is a recurring pattern: untrusted input flowing into a powerful feature that evaluates or resolves it. The same shape appears in template injection, expression-language injection, and unsafe deserialization across many languages and frameworks. Defenders who internalise the Log4Shell chain develop a useful instinct, becoming wary whenever attacker-controlled text can reach a component that loads code, resolves names, or evaluates expressions. That instinct, more than any single signature, is what prepares a team for the next event of this kind. Keeping an accurate software inventory, the ability to scan quickly, and standing log detections in place turns a future emergency into a routine, well-rehearsed response rather than a scramble.

Conclusion

Log4Shell is a maximum-severity remote code execution flaw in Log4j 2.0-beta9 through 2.14.1, triggered when attacker-controlled text containing a JNDI lookup token gets logged. The defence is straightforward in principle: inventory, scan, hunt your logs, and upgrade to a fixed release. Start by running the Log4Shell scanner at log4shell.tools to find out where you stand.