Analysis of the log4j2 JNDI Injection Vulnerability

Summary0x01 Preface. December 9, 2021 became a sleepless night when Apache Log4j was found vulnerable to a simple, high-impact RCE. Countless components depend on Log4j2, apart from the risks in applications themselves. A Maven Repository search for projects using log4j-core 2.14…

jn'dilog4j2

0x01 Preface

December 9, 2021 became a sleepless night when Apache Log4j was found vulnerable to a simple, high-impact RCE. Countless components depend on Log4j2, apart from the risks in applications themselves. Maven Repository alone listed many pages of projects using log4j-core 2.14.1:

1.png

This article briefly analyzes the vulnerability's mechanics.

0x02 Affected Scope

Applications or components using apache-log4j-core 2.x before 2.15.0-rc2

0x03 Analysis

According to the official revision notes:https://issues.apache.org/jira/projects/LOG4J2/issues/LOG4J2-3201?filter=allissues

2.png

The RCE clearly uses LDAP injection through JNDI. Inspect the patch history:

3.png

We can see thatlookupfunction added a validation check.

Once the vulnerability class is known, begin with the official documentation forlookupdocumentation:

4.png

lookupprovides a way to add values to Log4j2 configuration from anywhere and implementsStrLookupplugin type. The official documentation shows that Log4j2 supports many lookup methods:

5.png

The complete set is:base64datactxmainenvsyssdjavamarkerjndijvmrunargsmapbundlelog4j

Because the focus here is JNDI lookup, the other lookup methods are omitted.

The official documentation describes JNDI lookup:

6.png

JndiLookup retrieves variables through JNDI. The documentation provides this example:

TEXT
<File name="Application" fileName="application.log">
	<PatternLayout>
		<pattern>%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n</pattern>
	</PatternLayout>
</File>

The earlier diagram of supported Log4j2 methods already reveals the JNDI syntax:

TEXT
${jndi:JNDIContent}

Now that we have identifiedlookupis the trigger point, and we found a way to invoke lookupmethod, the next step is to find an entry point. Supplying a JNDI LDAP lookup at that entry point can produce RCE.

Which entry point can receive${jndi:JNDIContent}?

Exactly: it isLogManager.getLogger().xxxx()method

Log4j2 has eight log levels, selected throughLogManager.getLogger()The logging methods are called as follows:

TEXT
LogManager.getLogger().error()
LogManager.getLogger().fatal()

LogManager.getLogger().trace()
LogManager.getLogger().traceExit()
LogManager.getLogger().traceEntry()
LogManager.getLogger().info()
LogManager.getLogger().warn()
LogManager.getLogger().debug()
LogManager.getLogger().log()
LogManager.getLogger().printf()

In the list above,error()andfatal()can trigger the vulnerability under the default configuration; other methods require the corresponding log level because inlogIfEnabledchecks the current log level:

7.png

Only whenthe current event's log levelgreater than or equal toconfigured log level, the condition is satisfied and execution enterslogMessage()method

8.png

With those fundamentals in place, the trigger mechanism can be analyzed.

The test case is:

JAVA
public class log4j {

    private static final Logger logger = LogManager.getLogger();

    public static void main(String[] args) {
        Collection<org.apache.logging.log4j.core.Logger> current = LoggerContext.getContext(false).getLoggers();
        Collection<org.apache.logging.log4j.core.Logger> notcurrent = LoggerContext.getContext().getLoggers();
        Collection<org.apache.logging.log4j.core.Logger> allConfig = current;
        allConfig.addAll(notcurrent);
        for (org.apache.logging.log4j.core.Logger log:allConfig){
            log.setLevel(Level.ALL);
        }
        logger.error(Level.ALL,"payload");
//        logger.warn("payload");
//        logger.info("payload");
//        logger.debug("payload");
//        logger.traceExit("payload");
//        logger.trace("payload");
//        logger.fatal("payload");
//        logger.printf(Level.ALL,"payload");
//        logger.traceEntry("payload");
//        logger.log(Level.ALL,"payload");
 }
}

All these logging methods trigger the flaw through the same mechanism, so Error is used as the example.

The Error class hierarchy shows that it ultimately callsAbstractLogger.javainpublic void error()method:

9.png

This method callslogIfEnabledchecks whether the event meets the configured log level and, if so, performslogMessageoperation:

10.png

The later, nonessential call path is:

logMessage ----> logMessageSafely ----> logMessageTrackRecursion ----> tryLogMessage ----> log

----> DefaultReliabilityStrategy.log ----> loggerConfig.log ----> processLogEvent ----> callAppenders ----> tryCallAppender ----> append ----> tryAppend ----> directEncodeEvent ----> encode ----> toText ----> toSerializable ---->format----> PatternFormatter.format

The first key point is inPatternFormatter.javain formatmethod:

11.png

If it detects$character followed by a{character, it processes the input up to}parses the content in between andreplace

replace --> substitute --> StrSubstitutor.substitute --> resolveVariable --> Interpolator.lookup

inInterpolator.lookupfirst extracts the string prefix:

12.png

If an internal method matches, processing enters the matching handler. Here, the JNDI handler is invoked byJndiLookupclass for further processing:

13.png

It ultimately loads an attacker-supplied LDAP server and returns a malicious JNDI Reference object, triggering RCE.

0x04 Reproduction

14.png

0x05 Closing Notes

The number of components and breadth of Log4j2's reach produced consequences that neither the discoverer nor the security account that first published a PoC likely anticipated. The vulnerability offers more than a new technique; it raises broader questions:

First, why did a vulnerability in a low-level Java dependency compromise so many major companies? A few vendors patched early, but the episode shows that supply-chain zero-days still cannot be defended against immediately. Can a new mechanism provide meaningful protection against supply-chain zero-days?

Second, JNDI lookup had existed in Log4j2 for seven years. It is sobering that a vulnerability with such broad reach and low exploitation complexity remained undiscovered for so long, despite JndiLookup usage being described in the official documentation.

Third, future zero-day research may indeed follow Skay's suggestion: find *.jar => add as library => shift+shift => find log4j => RCE This technique is not limited to Log4j; it is a useful way to search for vulnerabilities in dependencies generally.

Fourth, 15.png

References

https://logging.apache.org/log4j/2.x/manual/lookups.html https://github.com/apache/logging-log4j2/pull/608/commits/755e2c9d57f0517a73d16bfcaed93cc91969bdee