Neutralizing Your Inputs: A Log4Shell Weakness Story


The recent vulnerability in log4j (CVE-2021–44228), also called “Log4Shell,” has received a lot of attention because of how dangerous and widespread it is. Defenders in vulnerability management and incident response will likely be very busy with this one for a while.

While Log4Shell has been a hot topic, there hasn’t been much discussion about the original mistakes made, i.e., the weaknesses involved, and how the initial failure to address the root cause weakness led to additional pain (and multiple fixes) for defenders within the first week of disclosure. Also, you might have noticed how the same CVE was mapped to different CWEs by multiple sources. Let’s look a little deeper into the weakness that started it all.

Getting to Root Cause — The Initial Mistake

An important aspect of the vulnerability is that log4j can treat substrings of the form “${xyz}” as executable expressions. Here, the “$” and “{}” characters are special elements that change how the string is generated: the contents of the “${xyz}” sequence are executed like code. The mixture of control logic (or commands) with data in the same string or message is a commonly known, ubiquitous design flaw in many protocols, functions, and languages, which can make it very easy for programmers to introduce vulnerabilities where malicious data is treated as a command. It doesn’t take much imagination to see that things could get really bad, really quick.

As seen in many exploit examples for this vulnerability, the attacker can inject executable expressions by using the “${xyz}” style format. However, injection is an attack. The key weakness consists of log4j not “neutralizing” potentially dangerous inputs that contain special elements, which are later fed to a command interpreter. The CWE glossary defines neutralization as “the process of ensuring that input or output has certain security properties before it is used,” which broadly includes protection mechanisms such as filtering, canonicalization, validation, etc. The high-level class CWE-74 covers improper neutralization, with many well-known descendants related to SQL injection (CWE-89), OS command injection (CWE-78), and others.

For this log4j issue, the most precise CWE available is CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement (‘Expression Language Injection’). This entry could be found by navigating the sub-tree under CWE-74, or by knowing that the “${}” sequences are called expression language. Through the User Experience Working Group, we’re trying to find ways to make CWE easier to use, so that it’s easier to find the best match.

CWE Mapping — Your Mileage May Vary

So, why isn’t CWE-917 universally used for Log4Shell? Part of it comes down to the perspective of the person who is mapping to CWE and how deeply they have analyzed the mistake that created the vulnerability.

Consider a common exploit string that is injected into various inputs that are eventually processed by log4j:


As described elsewhere, this expression triggers a call to an adversary-controlled server that returns a Java class with malicious code that is then executed by log4j. This process effectively involves deserialization, so people might map this to CWE-502: Deserialization of Untrusted Data. However, the ability to inject code is a consequence (or technical impact) resulting from the exploitation of the actual weakness, which is allowing attacker-injected expressions to trigger the download of the malicious code in the first place.

There are also exploits that involve accessing sensitive information, such as using “${env:AWS_SECRET_ACCESS_KEY}” to read an environment variable containing a secret API key. Because information is leaked, somebody might map the issue to CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.

At best, for this particular vulnerability, CWE-502 and CWE-200 are “resultant” from the original mistake. They are part of a chain of insecure behaviors that are enabled by the “root cause” weakness, improperly neutralizing expressions (i.e., CWE-917). If an expression language is very powerful, dozens of CWEs could be “resultant” from the mistake of simply allowing the expression language to be processed. Our CWE mapping guidance generally discourages mapping to a CWE if it’s effectively a “technical impact” stemming directly from a root cause CWE, instead of a completely independent mistake.

Many people thinking about the root cause might simply regard this problem as “improper input validation” (CWE-20), but that’s a very general concept — a high-level class weakness type. Also, you can’t always validate whether incoming data is correct or not. Here, log4j is a general-purpose logging product that basically needs to accept log messages with arbitrary contents (since the messages will vary widely depending on the particular app using log4j). So, CWE-917 (as a base-level weakness) remains more precise and a better choice.

If you look at the fixes for this weakness, these special elements are basically treated as regular characters by simply not processing them anymore. Now THAT’s neutralization! Early fixes attempted to minimize what functionality the attackers can access, although it was found that some other expression variants could still be processed.

The Final Word — What to Do and Not Do

Many developers or other defenders might be tempted to choose a quick-and-dirty fix by using a denylist to reject any inputs that contain “jndi:ldap:” substrings. At best, this is a delaying tactic, not a complete fix, because denylists don’t always remove every possible “bad” input (CWE-184). For example, “${jndi:ldap:…” could be encoded as “${${::-j}ndi:…” — i.e. a nested expression would be called that would just produce the “j” in the “jndi”.

As we’ve seen with log4j, and with lots of other software, when addressing a vulnerability, it is important to identify and mitigate the root cause weakness that starts the problem and not one of the resultant weaknesses that appear later in the chain. There might be alternative ways to instantiate those weaknesses, leading to another round of patching and testing.

Lead author: Steve Battista. Support: Steve Christey Coley, Adam Chaudry, Marisa Harriston, and Alec Summers. +