Log4j Vulnerability CVE-2021-45105: What You Need to Know
Table of Contents
A third Log4j2 vulnerability was disclosed the night between Dec 17 and 18 by the Apache security team, and was given the ID of CVE-2021-45105.
According to the security advisory, 2.16.0, which fixed the two previous vulnerabilities, is susceptible to a DoS attack caused by a Stack-Overflow in Context Lookups in the configuration file’s layout patterns.
What is this CVE about? What can you do to fix it? How does it differ from the previous CVEs?
Distinguishing between CVE-2021-45105 and previous Log4j CVEs
After disabling the JNDI functionality altogether, and removing the message lookup feature, 2.16.0 was thought to be unaffected by any further exploits using the Lookups in general.
However, although it prevented Remote Code Execution (RCE) and even Local Code Execution (LCE) exploits from taking place, it did not address crafted input that could manipulate the Context Lookup functionality into rendering an infinite recursion, the last leading to a stack-overflow and crash.
Background: String substitution in lookups
The StrSubstitutor and StrLookup classes in log4j-core are responsible for parsing Lookups that are made within layout patterns, such as ${ctx:username}
. When the substitutor attempts to resolve the username
lookup, it’ll evaluate the corresponding username
variable of the ThreadContext
map, and substitute it with its value (hence the name “substitutor”).
This all works, until someone messes with the values of variables in the ThreadContext
Map.
At first, setting ThreadContext’s username
variable to the string: ${ctx:username}
, would produce the following steps for the substitutor, resulting in an infinite loop:
${ctx: }
tells him to look up for the value after the colon in theThreadContext
map (ctx
). The value after the colon isusername
.- The substitutor will consequently find the
username
variable in theThreadContext
map, whose value is${ctx:username}
(yes, the same string as in the layout pattern itself. Here lies the problem), and replace theusername
variable name with it. - Now, the string in the layout pattern has remained the same,
${ctx:username}
, thus the substitutor will again fetch theusername
variable’s value fromThreadContext
, and will do it again and again in an infinite loop.
This did not crash the entire application as previously stated about CVE-2021-45046, before escalating its severity to Critical. It merely throws a java.lang.IllegalStateException
, thanks to StrSubstitutor’s checkCyclicSubstitution
method.
However, it was later discovered that one could use another feature of the lookup format and trigger an infinite loop that is not detected by Log4j. This will result in a java.lang.StackOverflowError
, and cause an application Denial-of-Service. The vulnerable feature is the Lookup default value.
Understanding a CVE-2021-45105 Exploit
The Lookup pattern accepts the following format:
${lookupName:key:-defaultValue}
lookupName
is the name, or type, of the lookup to perform (examples arectx, env
, etc.).key
is the name of the variable to look for in the corresponding map object (inctx
case, it is theThreadContext
map).defaultValue
is an optional value which tells the substitutor what to put in place of this lookup instance, ifkey
does not exist in the map.
There may be readers who already figured this out:
If a variable in a ThreadContext
map is attacker-controlled, one can use the default value to hold the same string as a Context
Lookup.
Here’s an example of what it looks like:
Given a vulnerable application whose Log4j configuration sets a custom layout pattern as follows:
%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} – %msg%n ${ctx:user}
And assuming an application user can control the user variable, which is stored in the ThreadContext
map.
An attacker can set the value of user to the following, and trigger a stack-overflow.:
${ctx:user1:-${ctx:user}}
How does it work?
- The substitutor finds the value of
user
within the context map, and replacesuser
with it. The value is${ctx:user1:-${ctx:user}}
as stated earlier. - It then tries to evaluate the resulting lookup pattern that was the value of
user
. This is possible because of the recursive functionality of lookup patterns. user1
does not exist inThreadContext
, so the substitutor goes forward to the default value as last resort.- The default value is a string that in itself is a lookup pattern,
${ctx:user}
, referencing back to theuser
variable. Note that it’s the same as the original lookup in the configuration layout pattern! This means that the substitutor will forever try to resolve the variables, calling itssubstitute
method again and again until the stack-buffer overflows.
Other payloads were also found to trigger DoS in the same manner, one of them was mentioned by Ross Cohen, which created the ticket tracking this vulnerability:
${${::-${::-$${::-j}}}}
CVE-2021-45105: The fix
Apache have since released a fix to mitigate this vulnerability — 2.17.0, which fixed the StrSubstitutor
logic, and prevented cases in which there is a recursion in lookup within the string itself:
2.17.0 also restricted recursive lookups that originate from the lookup value itself:
Remediating CVE-2021-45105
It is highly recommended for users of Log4j to upgrade to the latest 2.17.0 version.
If it is not possible at the moment, make sure your Log4j version is at least upgraded to 2.16.0, and ensure you are not using any Context lookups of the form:
${ctx:username}
You can switch such lookups into Thread Context Map patterns, such as:
%X, %mdc
, or %MDC
If Context Lookups are mandatory, ensure that there are no such lookups that reference data that is user-controlled in any way.
Staying ahead of Log4j vulnerabilities
In order to make sure that your dependencies are updated and secure, we recommend you:
- Keep your open source components up to date to make sure direct dependencies are automatically patched to the latest version.
- Add an integration to your repository, so that when a vulnerability is detected, you get a GitHub issue and a PR is opened automatically.
Integrating automated security into your repo, so that issues are addressed as soon as possible, is the best way to mitigate open source risks early, before they hit the headlines.
Want to find and fix vulnerable versions of Log4j in your code? Learn about our free CLI tool, or download it now.