From 7fe72d62fcb9246be792b946e405e1d40d402780 Mon Sep 17 00:00:00 2001 From: Ralph Goers Date: Sat, 4 Dec 2021 20:59:39 -0700 Subject: [PATCH] LOG4J2-3201 - Limit the protocols JNDI can use by default. Limit the servers and classes that can be accessed via LDAP. --- .../logging/log4j/core/net/JndiManager.java | 62 ++++++++++--------- .../logging/log4j/core/util/NetUtils.java | 4 ++ src/changes/changes.xml | 3 + src/site/xdoc/manual/appenders.xml | 27 ++++++++ src/site/xdoc/manual/configuration.xml.vm | 26 ++++++++ src/site/xdoc/manual/extending.xml | 5 +- src/site/xdoc/manual/lookups.xml | 7 +++ 7 files changed, 103 insertions(+), 31 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java index b392b93..2d7604f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java @@ -209,43 +209,45 @@ public class JndiManager extends AbstractManager { public synchronized T lookup(final String name) throws NamingException { try { URI uri = new URI(name); - if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) { - LOGGER.warn("Log4j JNDI does not allow protocol {}", uri.getScheme()); - return null; - } - if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) { - if (!allowedHosts.contains(uri.getHost())) { - LOGGER.warn("Attempt to access ldap server not in allowed list"); + if (uri.getScheme() != null) { + if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) { + LOGGER.warn("Log4j JNDI does not allow protocol {}", uri.getScheme()); return null; } - Attributes attributes = this.context.getAttributes(name); - if (attributes != null) { - // In testing the "key" for attributes seems to be lowercase while the attribute id is - // camelcase, but that may just be true for the test LDAP used here. This copies the Attributes - // to a Map ignoring the "key" and using the Attribute's id as the key in the Map so it matches - // the Java schema. - Map attributeMap = new HashMap<>(); - NamingEnumeration enumeration = attributes.getAll(); - while (enumeration.hasMore()) { - Attribute attribute = enumeration.next(); - attributeMap.put(attribute.getID(), attribute); + if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) { + if (!allowedHosts.contains(uri.getHost())) { + LOGGER.warn("Attempt to access ldap server not in allowed list"); + return null; } - Attribute classNameAttr = attributeMap.get(CLASS_NAME); - if (attributeMap.get(SERIALIZED_DATA) != null) { - if (classNameAttr != null) { - String className = classNameAttr.get().toString(); - if (!allowedClasses.contains(className)) { - LOGGER.warn("Deserialization of {} is not allowed", className); + Attributes attributes = this.context.getAttributes(name); + if (attributes != null) { + // In testing the "key" for attributes seems to be lowercase while the attribute id is + // camelcase, but that may just be true for the test LDAP used here. This copies the Attributes + // to a Map ignoring the "key" and using the Attribute's id as the key in the Map so it matches + // the Java schema. + Map attributeMap = new HashMap<>(); + NamingEnumeration enumeration = attributes.getAll(); + while (enumeration.hasMore()) { + Attribute attribute = enumeration.next(); + attributeMap.put(attribute.getID(), attribute); + } + Attribute classNameAttr = attributeMap.get(CLASS_NAME); + if (attributeMap.get(SERIALIZED_DATA) != null) { + if (classNameAttr != null) { + String className = classNameAttr.get().toString(); + if (!allowedClasses.contains(className)) { + LOGGER.warn("Deserialization of {} is not allowed", className); + return null; + } + } else { + LOGGER.warn("No class name provided for {}", name); return null; } - } else { - LOGGER.warn("No class name provided for {}", name); + } else if (attributeMap.get(REFERENCE_ADDRESS) != null + || attributeMap.get(OBJECT_FACTORY) != null) { + LOGGER.warn("Referenceable class is not allowed for {}", name); return null; } - } else if (attributeMap.get(REFERENCE_ADDRESS) != null - || attributeMap.get(OBJECT_FACTORY) != null) { - LOGGER.warn("Referenceable class is not allowed for {}", name); - return null; } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java index b9a01ae..6adac59 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java @@ -83,6 +83,10 @@ public final class NetUtils { } } + /** + * Returns all the local host names and ip addresses. + * @return The local host names and ip addresses. + */ public static List getLocalIps() { List localIps = new ArrayList<>(); localIps.add("localhost"); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9c44684..f2d9c57 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -30,6 +30,9 @@ - "remove" - Removed --> + + Limit the protocols JNDI can use by default. Limit the servers and classes that can be accessed via LDAP. + Implement requiresLocation in GelfLayout to reflect whether location information is used in the message Pattern. diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml index ab40094..267f54f 100644 --- a/src/site/xdoc/manual/appenders.xml +++ b/src/site/xdoc/manual/appenders.xml @@ -1542,6 +1542,33 @@ public class ConnectionFactory { Default Description + + allowdLdapClasses + String + null + + A comma separated list of fully qualified class names that may be accessed by LDAP. The classes + must implement Serializable. Only applies when the JMS Appender By default only Java primative classes are allowed. + + + + allowdLdapHosts + String + null + + A comma separated list of host names or ip addresses that may be accessed by LDAP. By default only + the local host names and ip addresses are allowed. + + + + allowdJndiProtocols + String + null + + A comma separated list of protocol names that JNDI will allow. By default only java, ldap, and ldaps + are the only allowed protocols. + + factoryBindingName String diff --git a/src/site/xdoc/manual/configuration.xml.vm b/src/site/xdoc/manual/configuration.xml.vm index a3da3b8..402a96c 100644 --- a/src/site/xdoc/manual/configuration.xml.vm +++ b/src/site/xdoc/manual/configuration.xml.vm @@ -1960,6 +1960,32 @@ public class AwesomeTest { before falling back to the default class loader. + + log4j2.allowedLdapClasses + LOG4J_ALLOWED_LDAP_CLASSES +   + + System property that specifies fully qualified class names that may be accessed by LDAP. The classes + must implement Serializable. By default only Java primative classes are allowed. + + + + log4j2.allowedLdapHosts + LOG4J_ALLOWED_LDAP_HOSTS +   + + System property that adds host names or ip addresses that may be access by LDAP. By default it only allows + the local host names and ip addresses. + + + + log4j2.allowedJndiProtocols + LOG4J_ALLOWED_JNDI_PROTOCOLS +   + + System property that adds protocol names that JNDI will allow. By default it only allows java, ldap, and ldaps. + + log4j2.uuidSequence
diff --git a/src/site/xdoc/manual/extending.xml b/src/site/xdoc/manual/extending.xml index ba04d68..04c742a 100644 --- a/src/site/xdoc/manual/extending.xml +++ b/src/site/xdoc/manual/extending.xml @@ -92,7 +92,10 @@
Associates LoggerContexts with the ClassLoader that created the caller of the getLogger call. This is the default ContextSelector.
JndiContextSelector
-
Locates the LoggerContext by querying JNDI.
+
Locates the LoggerContext by querying JNDI. Please see log4j2.allowedJndiProtocols, + log4j2.allowedLdapClasses, and + log4j2.allowedLdapHosts for restrictions on using JNDI + with Log4j.
AsyncLoggerContextSelector
Creates a LoggerContext that ensures that all loggers are AsyncLoggers.
BundleContextSelector
diff --git a/src/site/xdoc/manual/lookups.xml b/src/site/xdoc/manual/lookups.xml index d699e78..cc6a66f 100644 --- a/src/site/xdoc/manual/lookups.xml +++ b/src/site/xdoc/manual/lookups.xml @@ -270,6 +270,13 @@ The JndiLookup allows variables to be retrieved via JNDI. By default the key will be prefixed with java:comp/env/, however if the key contains a ":" no prefix will be added.

+

By default the JDNI Lookup only supports the java, ldap, and ldaps protocols or no protocol. Additional + protocols may be supported by specifying them on the log4j2.allowedJndiProtocols property. + When using LDAP Java classes that implement the Referenceable interface are not supported for security + reasons. Only the Java primative classes are supported by default as well as any classes specified by the + log4j2.allowedLdapClasses property. When using LDAP only references to the local host name + or ip address are supported along with any hosts or ip addresses listed in the + log4j2.allowedLdapHosts property.


   
-- 
2.27.0