Skip to content

Commit

Permalink
LOG4J2-3208 - Disable JNDI by default
Browse files Browse the repository at this point in the history
  • Loading branch information
rgoers committed Dec 11, 2021
1 parent 5aa6e95 commit c362aff
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 31 deletions.
Expand Up @@ -124,10 +124,15 @@ private static class JmsManagerFactory implements ManagerFactory<JmsManager, Jms

@Override
public JmsManager createManager(final String name, final JmsManagerConfiguration data) {
try {
return new JmsManager(name, data);
} catch (final Exception e) {
logger().error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e);
if (JndiManager.isIsJndiEnabled()) {
try {
return new JmsManager(name, data);
} catch (final Exception e) {
logger().error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e);
return null;
}
} else {
logger().error("JNDI has not been enabled. The log4j2.enableJndi property must be set to true");
return null;
}
}
Expand Down
Expand Up @@ -26,6 +26,7 @@
import org.apache.logging.log4j.core.config.ConfigurationAware;
import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.net.JndiManager;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.core.util.ReflectionUtil;
import org.apache.logging.log4j.status.StatusLogger;
Expand Down Expand Up @@ -77,7 +78,9 @@ public Interpolator(final StrLookup defaultLookup, final List<String> pluginPack
for (final Map.Entry<String, PluginType<?>> entry : plugins.entrySet()) {
try {
final Class<? extends StrLookup> clazz = entry.getValue().getPluginClass().asSubclass(StrLookup.class);
strLookupMap.put(entry.getKey().toLowerCase(), ReflectionUtil.instantiate(clazz));
if (!clazz.getName().equals(JndiLookup.class.getName()) || JndiManager.isIsJndiEnabled()) {
strLookupMap.put(entry.getKey().toLowerCase(), ReflectionUtil.instantiate(clazz));
}
} catch (final Throwable t) {
handleError(entry.getKey(), t);
}
Expand Down Expand Up @@ -106,12 +109,14 @@ public Interpolator(final Map<String, String> properties) {
strLookupMap.put("lower", new LowerLookup());
strLookupMap.put("upper", new UpperLookup());
// JNDI
try {
// [LOG4J2-703] We might be on Android
strLookupMap.put(LOOKUP_KEY_JNDI,
Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class));
} catch (final LinkageError | Exception e) {
handleError(LOOKUP_KEY_JNDI, e);
if (JndiManager.isIsJndiEnabled()) {
try {
// [LOG4J2-703] We might be on Android
strLookupMap.put(LOOKUP_KEY_JNDI,
Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class));
} catch (final LinkageError | Exception e) {
handleError(LOOKUP_KEY_JNDI, e);
}
}
// JMX input args
try {
Expand Down
Expand Up @@ -73,6 +73,10 @@ public class JndiManager extends AbstractManager {

private final DirContext context;

public static boolean isIsJndiEnabled() {
return PropertiesUtil.getProperties().getBooleanProperty("log4j2.enableJndi", false);
}

private JndiManager(final String name, final DirContext context, final List<String> allowedHosts,
final List<String> allowedClasses, final List<String> allowedProtocols) {
super(null, name);
Expand All @@ -82,6 +86,14 @@ private JndiManager(final String name, final DirContext context, final List<Stri
this.allowedProtocols = allowedProtocols;
}

private JndiManager(final String name) {
super(null, name);
this.context = null;
this.allowedProtocols = null;
this.allowedClasses = null;
this.allowedHosts = null;
}

/**
* Gets the default JndiManager using the default {@link javax.naming.InitialContext}.
*
Expand Down Expand Up @@ -194,7 +206,10 @@ public static Properties createProperties(final String initialContextFactoryName

@Override
protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
return JndiCloser.closeSilently(this.context);
if (context != null) {
return JndiCloser.closeSilently(this.context);
}
return true;
}

/**
Expand All @@ -207,6 +222,9 @@ protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
*/
@SuppressWarnings("unchecked")
public synchronized <T> T lookup(final String name) throws NamingException {
if (context == null) {
return null;
}
try {
URI uri = new URI(name);
if (uri.getScheme() != null) {
Expand Down Expand Up @@ -262,21 +280,25 @@ private static class JndiManagerFactory implements ManagerFactory<JndiManager, P

@Override
public JndiManager createManager(final String name, final Properties data) {
String hosts = data != null ? data.getProperty(ALLOWED_HOSTS) : null;
String classes = data != null ? data.getProperty(ALLOWED_CLASSES) : null;
String protocols = data != null ? data.getProperty(ALLOWED_PROTOCOLS) : null;
List<String> allowedHosts = new ArrayList<>();
List<String> allowedClasses = new ArrayList<>();
List<String> allowedProtocols = new ArrayList<>();
addAll(hosts, allowedHosts, permanentAllowedHosts, ALLOWED_HOSTS, data);
addAll(classes, allowedClasses, permanentAllowedClasses, ALLOWED_CLASSES, data);
addAll(protocols, allowedProtocols, permanentAllowedProtocols, ALLOWED_PROTOCOLS, data);
try {
return new JndiManager(name, new InitialDirContext(data), allowedHosts, allowedClasses,
allowedProtocols);
} catch (final NamingException e) {
LOGGER.error("Error creating JNDI InitialContext.", e);
return null;
if (isIsJndiEnabled()) {
String hosts = data != null ? data.getProperty(ALLOWED_HOSTS) : null;
String classes = data != null ? data.getProperty(ALLOWED_CLASSES) : null;
String protocols = data != null ? data.getProperty(ALLOWED_PROTOCOLS) : null;
List<String> allowedHosts = new ArrayList<>();
List<String> allowedClasses = new ArrayList<>();
List<String> allowedProtocols = new ArrayList<>();
addAll(hosts, allowedHosts, permanentAllowedHosts, ALLOWED_HOSTS, data);
addAll(classes, allowedClasses, permanentAllowedClasses, ALLOWED_CLASSES, data);
addAll(protocols, allowedProtocols, permanentAllowedProtocols, ALLOWED_PROTOCOLS, data);
try {
return new JndiManager(name, new InitialDirContext(data), allowedHosts, allowedClasses,
allowedProtocols);
} catch (final NamingException e) {
LOGGER.error("Error creating JNDI InitialContext.", e);
return null;
}
} else {
return new JndiManager(name);
}
}

Expand Down
Expand Up @@ -93,6 +93,12 @@ public class JndiContextSelector implements NamedContextSelector {

private static final StatusLogger LOGGER = StatusLogger.getLogger();

public JndiContextSelector() {
if (!JndiManager.isIsJndiEnabled()) {
throw new IllegalStateException("JNDI must be enabled by setting log4j2.enableJndi=true");
}
}

@Override
public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
Expand Down
Expand Up @@ -49,6 +49,7 @@
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.message.StringMapMessage;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
Expand Down Expand Up @@ -83,6 +84,11 @@ public class JmsAppenderTest {
@Rule
public RuleChain rules = RuleChain.outerRule(jndiRule).around(ctx);

@BeforeClass
public static void beforeClass() throws Exception {
System.setProperty("log4j2.enableJndi", "true");
}

public JmsAppenderTest() throws Exception {
// this needs to set up before LoggerContextRule
given(connectionFactory.createConnection()).willReturn(connection);
Expand Down
Expand Up @@ -18,6 +18,7 @@

import java.io.File;
import java.util.Collections;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
Expand All @@ -29,6 +30,7 @@
import org.apache.logging.log4j.test.appender.ListAppender;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.RuleChain;
Expand All @@ -47,8 +49,12 @@ public class RoutingAppenderWithJndiTest {
public static LoggerContextRule loggerContextRule = new LoggerContextRule("log4j-routing-by-jndi.xml");

@ClassRule
public static RuleChain rules = RuleChain.outerRule(new JndiRule(Collections.<String, Object>emptyMap()))
.around(loggerContextRule);
public static RuleChain rules = RuleChain.outerRule(new JndiRule(initBindings())).around(loggerContextRule);

private static Map<String, Object> initBindings() {
System.setProperty("log4j2.enableJndi", "true");
return Collections.emptyMap();
}

@Before
public void before() throws NamingException {
Expand Down
Expand Up @@ -23,6 +23,7 @@

import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.junit.JndiRule;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
Expand All @@ -48,12 +49,14 @@ public class InterpolatorTest {
protected void before() throws Throwable {
System.setProperty(TESTKEY, TESTVAL);
System.setProperty(TESTKEY2, TESTVAL);
System.setProperty("log4j2.enableJndi", "true");
}

@Override
protected void after() {
System.clearProperty(TESTKEY);
System.clearProperty(TESTKEY2);
System.clearProperty("log4j2.enableJndi");
}
}).around(new JndiRule(
JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME));
Expand Down
@@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.lookup;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.logging.log4j.junit.JndiRule;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

/**
* JndiDisabledLookupTest
*
* Verifies the Lookups are disabled without the log4j2.enableJndi property set to true.
*/
public class JndiDisabledLookupTest {

private static final String TEST_CONTEXT_RESOURCE_NAME = "logging/context-name";
private static final String TEST_CONTEXT_NAME = "app-1";
private static final String TEST_INTEGRAL_NAME = "int-value";
private static final int TEST_INTEGRAL_VALUE = 42;
private static final String TEST_STRINGS_NAME = "string-collection";
private static final Collection<String> TEST_STRINGS_COLLECTION = Arrays.asList("one", "two", "three");

@Rule
public JndiRule jndiRule = new JndiRule(createBindings());

private Map<String, Object> createBindings() {
final Map<String, Object> map = new HashMap<>();
map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME);
map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_INTEGRAL_NAME, TEST_INTEGRAL_VALUE);
map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_STRINGS_NAME, TEST_STRINGS_COLLECTION);
return map;
}

@Test
public void testLookup() {
final StrLookup lookup = new JndiLookup();

String contextName = lookup.lookup(TEST_CONTEXT_RESOURCE_NAME);
assertNull(contextName);
}
}
Expand Up @@ -22,6 +22,7 @@
import java.util.Map;

import org.apache.logging.log4j.junit.JndiRule;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;

Expand All @@ -42,6 +43,11 @@ public class JndiLookupTest {
@Rule
public JndiRule jndiRule = new JndiRule(createBindings());

@BeforeClass
public static void beforeClass() {
System.setProperty("log4j2.enableJndi", "true");
}

private Map<String, Object> createBindings() {
final Map<String, Object> map = new HashMap<>();
map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME);
Expand Down
Expand Up @@ -54,6 +54,7 @@ public class JndiRestrictedLookupTest {
public static void beforeClass() {
System.setProperty("log4j2.allowedLdapClasses", Level.class.getName());
System.setProperty("log4j2.allowedJndiProtocols", "dns");
System.setProperty("log4j2.enableJndi", "true");
}

@Test
Expand Down
3 changes: 3 additions & 0 deletions src/changes/changes.xml
Expand Up @@ -30,6 +30,9 @@
- "remove" - Removed
-->
<release version="2.15.0" date="2021-12-XX" description="GA Release 2.15.1">
<action issue="LOG4J2-3208" dev="rgoers" type="fix">
Disable JNDI by default. Require log4j2.enableJndi to be set to true to allow JNDI.
</action>
</release>
<release version="2.15.0" date="2021-12-06" description="GA Release 2.15.0">
<!-- ADDS -->
Expand Down
2 changes: 1 addition & 1 deletion src/site/markdown/index.md.vm
Expand Up @@ -142,7 +142,7 @@ The Log4j team has been made aware of a security vulnerability, CVE-2021-44228,

Log4j’s JNDI support has not restricted what names could be resolved. Some protocols are unsafe or can allow remote code
execution. Log4j now limits the protocols by default to only java, ldap, and ldaps and limits the ldap protocols to only
accessing Java primitive objects by default served on the local host.</p>
accessing Java primitive objects by default served on the local host.

One vector that allowed exposure to this vulnerability was Log4j’s allowance of Lookups to appear in log messages. As of
Log4j 2.15.0 this feature is now disabled by default. While an option has been provided to enable Lookups in this fashion,
Expand Down
2 changes: 1 addition & 1 deletion src/site/markdown/security.md
Expand Up @@ -64,7 +64,7 @@ substitution is enabled. From log4j 2.15.0, this behavior has been disabled by d
Mitigation: In releases >=2.10, this behavior can be mitigated by setting either the system property
`log4j2.formatMsgNoLookups` or the environment variable `LOG4J_FORMAT_MSG_NO_LOOKUPS` to `true`.
For releases from 2.7 through 2.14.1 all PatternLayout patterns can be modified to specify the message converter as
`%m{nnolookups}` instead of just `%m`.
`%m{nolookups}` instead of just `%m`.
For releases from 2.0-beta9 to 2.7, the only mitigation is to remove the `JndiLookup` class from the classpath:
`zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class`.

Expand Down
3 changes: 3 additions & 0 deletions src/site/xdoc/manual/appenders.xml
Expand Up @@ -1540,6 +1540,9 @@ public class ConnectionFactory {
<a name="JMSTopicAppender"/>
<subsection name="JMS Appender">
<p>The JMS Appender sends the formatted log event to a JMS Destination.</p>
<p>The JMS Appender requires JNDI support so as of release 2.15.1 this appender will not function unless
<code>log4j2.enableJndi=true</code>log4j2.enableJndi=true is configured as a system property or environment
variable. See the <a href="./configuration.html#enableJndi">enableJndi</a> system property.</p>
<p>
Note that in Log4j 2.0, this appender was split into a JMSQueueAppender and a JMSTopicAppender. Starting
in Log4j 2.1, these appenders were combined into the JMS Appender which makes no distinction between queues
Expand Down
8 changes: 8 additions & 0 deletions src/site/xdoc/manual/configuration.xml.vm
Expand Up @@ -2148,6 +2148,14 @@ public class AwesomeTest {
before falling back to the default class loader.
</td>
</tr>
<tr>
<td><a name="enableJndi"/>log4j2.enableJndi</td>
<td>LOG4J_ENABLE_JNDI</td>
<td>false</td>
<td>
When true, Log4j components that use JNDI are enabled. When false, the default, they are disabled.
</td>
</tr>
<tr>
<td><a name="allowedLdapClasses"/>log4j2.allowedLdapClasses</td>
<td>LOG4J_ALLOWED_LDAP_CLASSES</td>
Expand Down
3 changes: 3 additions & 0 deletions src/site/xdoc/manual/logsep.xml
Expand Up @@ -111,6 +111,9 @@
to use JNDI to locate each web application's <code>LoggerContext</code>. Be sure to set the
<code>isLog4jContextSelectorNamed</code> context parameter to <kbd>true</kbd> and also set the
<code>log4jContextName</code> and <code>log4jConfiguration</code> context parameters.
Note that the JndiContextSelector will not work unless <code>log4j2.enableJndi=true</code> is set as a
system property or environment variable. See the
<a href="./configuration.html#enableJndi">enableJndi</a> system property.
</li>
</ol>
<p>
Expand Down

0 comments on commit c362aff

Please sign in to comment.