CVE-2021-45105: Denial of Service via Uncontrolled Recursion in Log4j StrSubstitutor

December 18, 2021 | Trend Micro Research Team

In this excerpt of a Trend Micro Vulnerability Research Service vulnerability report, Guy Lederfein of the Trend Micro Research Team details a Denial-of-Service (DoS) bug discovered in Apache Log4j. This vulnerability should not be considered a variant of the bug known as “Log4Shell” (CVE-2021-44228), although it does abuse a similar attack vector. They are similar in that this bug also abuses attacker-controlled lookups in logged data. However, in this case, non-JNDI lookups can be abused. The following is a portion of his write-up covering the root cause CVE-2021-45105 with a few minimal modifications.


The Apache Log4j API supports variable substitution in lookups. However, a crafted variable can cause the application to crash due to uncontrolled recursive substitutions. An attacker with control over lookup commands (e.g., via the Thread Context Map) can craft a malicious lookup variable, which results in a Denial-of-Service (DoS) attack. This has been tested and confirmed on Log4j versions up to and including 2.16.0.

The Vulnerability

When a nested variable is substituted by the StrSubstitutor class, it recursively calls the substitute() class. However, when the nested variable references the variable being replaced, the recursion is called with the same string. This leads to an infinite recursion and a DoS condition on the server. As an example, if the Pattern Layout contains a Context Lookup of ${ctx.apiversion}, and its assigned value is ${${ctx.apiversion}}, the variable will be recursively substituted with itself.

The Code Flow

The StrSubstitutor.substitute() method is called with the variable to be substituted:

The StrSubstitutor.substitute() method is called with the original variable lookup (i.e., ctx.apiversion):

In this call to StrSubstitutor.substitute(), a call to StrSubstitutor. checkCyclicSubstitution() is made:

Note that the method StrSubstitutor. checkCyclicSubstitution() attempts to detect cyclic substitutions of variables by maintaining a priorVariables list and comparing the current variable to the list:

Later, the variable is resolved to its value (i.e., ${${ctx:apiversion}}) and a recursive call to StrSubstitutor.substitute() is made:

Once again, we detect the variable in the value being parsed. However, the recursive call to StrSubstitutor.substitute() does not include the priorVariables list. Therefore, the StrSubstitutor. checkCyclicSubstitution() method will fail to detect the cyclic substitution and an infinite recursion will occur:

Note too that even if the cyclic substitution is caught by StrSubstitutor. checkCyclicSubstitution(), the exception thrown will only be caught by AppenderControl.TryCallAppender(), resulting in a failed write to the log:

Patch Analysis

In version 2.17.0, two classes were created that inherit from StrSubstitutor: ConfigurationStrSubstitutor, which only parses string substitutions in configuration parameters, and RuntimeStrSubstitutor, which parses strings that may contain user-provided input. With RuntimeStrSubstitutor, no recursive evaluation is allowed. This is enforced by the StrSubstitutor.substitute() method:

In addition, all recursive calls to StrSubstitutor.substitute() maintain the list of priorVariables, allowing to catch cases of cyclic substitutions by the StrSubstitutor.isCyclicSubstitution() method:

Proof of Concept

For demonstration purposes, we created a typical vulnerable application named log4j-vulnerable-app.jar, compiled with log4j version 2.16.0. The application is configured with a custom Pattern Layout, which contains a Context Lookup (${ctx:apiversion}). The application implements an HTTP server and sets the received X-Api-Version header as the value of the apiversion variable in the Thread Context Map. Afterwards, it logs a message.

The vulnerable app can be run as follows on the target server:
          java -jar log4j-vulnerable-app.jar

The poc.py script can be run as follows:
          python poc.py client <host>

where is the host running the vulnerable application.

Upon running the script, an HTTP request with the X-Api-Version header set to a value of ${${ctx:apiversion}} will be sent to the vulnerable application. When attempting to log the message, the vulnerable application will crash with the following stack trace:

Conclusion

 A patch for this bug was released by Apache on December 18, 2021. While Apache does list mitigating factors, we recommend upgrading to the latest version to ensure this vulnerability is completely addressed. This component has received quite a bit of attention this week, so it would not be a surprise to see further bugs disclosed – with or without a patch.

Special thanks to Guy Lederfein of the Trend Micro Research Team for providing such a thorough analysis of this vulnerability. For an overview of Trend Micro Research services please visit http://go.trendmicro.com/tis/.

The threat research team will be back with other great vulnerability analysis reports in the future. Until then, follow the ZDI team for the latest in exploit techniques and security patches.