|
17 |
| 17 |
|
18 | package org.apache.logging.log4j.core.net; | 18 | package org.apache.logging.log4j.core.net; |
19 |
| 19 |
|
| | 20 | +import java.net.URI; |
| | 21 | +import java.net.URISyntaxException; |
| | 22 | +import java.util.ArrayList; |
| | 23 | +import java.util.Arrays; |
| | 24 | +import java.util.HashMap; |
| | 25 | +import java.util.List; |
| | 26 | +import java.util.Locale; |
| | 27 | +import java.util.Map; |
20 | import java.util.Properties; | 28 | import java.util.Properties; |
21 | import java.util.concurrent.TimeUnit; | 29 | import java.util.concurrent.TimeUnit; |
22 |
| 30 |
|
23 | import javax.naming.Context; | 31 | import javax.naming.Context; |
24 | -import javax.naming.InitialContext; | 32 | +import javax.naming.NamingEnumeration; |
25 | import javax.naming.NamingException; | 33 | import javax.naming.NamingException; |
| | 34 | +import javax.naming.directory.Attribute; |
| | 35 | +import javax.naming.directory.Attributes; |
| | 36 | +import javax.naming.directory.DirContext; |
| | 37 | +import javax.naming.directory.InitialDirContext; |
26 |
| 38 |
|
27 | import org.apache.logging.log4j.core.appender.AbstractManager; | 39 | import org.apache.logging.log4j.core.appender.AbstractManager; |
28 | import org.apache.logging.log4j.core.appender.ManagerFactory; | 40 | import org.apache.logging.log4j.core.appender.ManagerFactory; |
29 | import org.apache.logging.log4j.core.util.JndiCloser; | 41 | import org.apache.logging.log4j.core.util.JndiCloser; |
| | 42 | +import org.apache.logging.log4j.core.util.NetUtils; |
| | 43 | +import org.apache.logging.log4j.util.PropertiesUtil; |
30 |
| 44 |
|
31 | /** | 45 | /** |
32 | - * Manages a JNDI {@link javax.naming.Context}. | 46 | + * Manages a JNDI {@link javax.naming.directory.DirContext}. |
33 | * | 47 | * |
34 | * @since 2.1 | 48 | * @since 2.1 |
35 | */ | 49 | */ |
36 | public class JndiManager extends AbstractManager { | 50 | public class JndiManager extends AbstractManager { |
37 |
| 51 |
|
| | 52 | + public static final String ALLOWED_HOSTS = "allowedLdapHosts"; |
| | 53 | + public static final String ALLOWED_CLASSES = "allowedLdapClasses"; |
| | 54 | + public static final String ALLOWED_PROTOCOLS = "allowedJndiProtocols"; |
| | 55 | + |
38 | private static final JndiManagerFactory FACTORY = new JndiManagerFactory(); | 56 | private static final JndiManagerFactory FACTORY = new JndiManagerFactory(); |
| | 57 | + private static final String PREFIX = "log4j2."; |
| | 58 | + private static final String LDAP = "ldap"; |
| | 59 | + private static final String LDAPS = "ldaps"; |
| | 60 | + private static final String JAVA = "java"; |
| | 61 | + private static final List<String> permanentAllowedHosts = NetUtils.getLocalIps(); |
| | 62 | + private static final List<String> permanentAllowedClasses = Arrays.asList(Boolean.class.getName(), |
| | 63 | + Byte.class.getName(), Character.class.getName(), Double.class.getName(), Float.class.getName(), |
| | 64 | + Integer.class.getName(), Long.class.getName(), Short.class.getName(), String.class.getName()); |
| | 65 | + private static final List<String> permanentAllowedProtocols = Arrays.asList(JAVA, LDAP, LDAPS); |
| | 66 | + private static final String SERIALIZED_DATA = "javaSerializedData"; |
| | 67 | + private static final String CLASS_NAME = "javaClassName"; |
| | 68 | + private static final String REFERENCE_ADDRESS = "javaReferenceAddress"; |
| | 69 | + private static final String OBJECT_FACTORY = "javaFactory"; |
| | 70 | + private final List<String> allowedHosts; |
| | 71 | + private final List<String> allowedClasses; |
| | 72 | + private final List<String> allowedProtocols; |
39 |
| 73 |
|
40 | - private final Context context; | 74 | + private final DirContext context; |
41 |
| 75 |
|
42 | - private JndiManager(final String name, final Context context) { | 76 | + private JndiManager(final String name, final DirContext context, final List<String> allowedHosts, |
| | 77 | + final List<String> allowedClasses, final List<String> allowedProtocols) { |
43 | super(null, name); | 78 | super(null, name); |
44 | this.context = context; | 79 | this.context = context; |
| | 80 | + this.allowedHosts = allowedHosts; |
| | 81 | + this.allowedClasses = allowedClasses; |
| | 82 | + this.allowedProtocols = allowedProtocols; |
45 | } | 83 | } |
46 |
| 84 |
|
47 | /** | 85 | /** |
@@ -168,21 +206,91 @@ protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { |
168 | * @throws NamingException if a naming exception is encountered | 206 | * @throws NamingException if a naming exception is encountered |
169 | */ | 207 | */ |
170 | @SuppressWarnings("unchecked") | 208 | @SuppressWarnings("unchecked") |
171 | - public <T> T lookup(final String name) throws NamingException { | 209 | + public synchronized <T> T lookup(final String name) throws NamingException { |
| | 210 | + try { |
| | 211 | + URI uri = new URI(name); |
| | 212 | + if (uri.getScheme() != null) { |
| | 213 | + if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) { |
| | 214 | + LOGGER.warn("Log4j JNDI does not allow protocol {}", uri.getScheme()); |
| | 215 | + return null; |
| | 216 | + } |
| | 217 | + if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) { |
| | 218 | + if (!allowedHosts.contains(uri.getHost())) { |
| | 219 | + LOGGER.warn("Attempt to access ldap server not in allowed list"); |
| | 220 | + return null; |
| | 221 | + } |
| | 222 | + Attributes attributes = this.context.getAttributes(name); |
| | 223 | + if (attributes != null) { |
| | 224 | + // In testing the "key" for attributes seems to be lowercase while the attribute id is |
| | 225 | + // camelcase, but that may just be true for the test LDAP used here. This copies the Attributes |
| | 226 | + // to a Map ignoring the "key" and using the Attribute's id as the key in the Map so it matches |
| | 227 | + // the Java schema. |
| | 228 | + Map<String, Attribute> attributeMap = new HashMap<>(); |
| | 229 | + NamingEnumeration<? extends Attribute> enumeration = attributes.getAll(); |
| | 230 | + while (enumeration.hasMore()) { |
| | 231 | + Attribute attribute = enumeration.next(); |
| | 232 | + attributeMap.put(attribute.getID(), attribute); |
| | 233 | + } |
| | 234 | + Attribute classNameAttr = attributeMap.get(CLASS_NAME); |
| | 235 | + if (attributeMap.get(SERIALIZED_DATA) != null) { |
| | 236 | + if (classNameAttr != null) { |
| | 237 | + String className = classNameAttr.get().toString(); |
| | 238 | + if (!allowedClasses.contains(className)) { |
| | 239 | + LOGGER.warn("Deserialization of {} is not allowed", className); |
| | 240 | + return null; |
| | 241 | + } |
| | 242 | + } else { |
| | 243 | + LOGGER.warn("No class name provided for {}", name); |
| | 244 | + return null; |
| | 245 | + } |
| | 246 | + } else if (attributeMap.get(REFERENCE_ADDRESS) != null |
| | 247 | + || attributeMap.get(OBJECT_FACTORY) != null) { |
| | 248 | + LOGGER.warn("Referenceable class is not allowed for {}", name); |
| | 249 | + return null; |
| | 250 | + } |
| | 251 | + } |
| | 252 | + } |
| | 253 | + } |
| | 254 | + } catch (URISyntaxException ex) { |
| | 255 | + // This is OK. |
| | 256 | + } |
172 | return (T) this.context.lookup(name); | 257 | return (T) this.context.lookup(name); |
173 | } | 258 | } |
174 |
| 259 |
|
175 | private static class JndiManagerFactory implements ManagerFactory<JndiManager, Properties> { | 260 | private static class JndiManagerFactory implements ManagerFactory<JndiManager, Properties> { |
176 |
| 261 |
|
177 | @Override | 262 | @Override |
178 | public JndiManager createManager(final String name, final Properties data) { | 263 | public JndiManager createManager(final String name, final Properties data) { |
| | 264 | + String hosts = data != null ? data.getProperty(ALLOWED_HOSTS) : null; |
| | 265 | + String classes = data != null ? data.getProperty(ALLOWED_CLASSES) : null; |
| | 266 | + String protocols = data != null ? data.getProperty(ALLOWED_PROTOCOLS) : null; |
| | 267 | + List<String> allowedHosts = new ArrayList<>(); |
| | 268 | + List<String> allowedClasses = new ArrayList<>(); |
| | 269 | + List<String> allowedProtocols = new ArrayList<>(); |
| | 270 | + addAll(hosts, allowedHosts, permanentAllowedHosts, ALLOWED_HOSTS, data); |
| | 271 | + addAll(classes, allowedClasses, permanentAllowedClasses, ALLOWED_CLASSES, data); |
| | 272 | + addAll(protocols, allowedProtocols, permanentAllowedProtocols, ALLOWED_PROTOCOLS, data); |
179 | try { | 273 | try { |
180 | - return new JndiManager(name, new InitialContext(data)); | 274 | + return new JndiManager(name, new InitialDirContext(data), allowedHosts, allowedClasses, |
| | 275 | + allowedProtocols); |
181 | } catch (final NamingException e) { | 276 | } catch (final NamingException e) { |
182 | LOGGER.error("Error creating JNDI InitialContext.", e); | 277 | LOGGER.error("Error creating JNDI InitialContext.", e); |
183 | return null; | 278 | return null; |
184 | } | 279 | } |
185 | } | 280 | } |
| | 281 | + |
| | 282 | + private void addAll(String toSplit, List<String> list, List<String> permanentList, String propertyName, |
| | 283 | + Properties data) { |
| | 284 | + if (toSplit != null) { |
| | 285 | + list.addAll(Arrays.asList(toSplit.split("\\s*,\\s*"))); |
| | 286 | + data.remove(propertyName); |
| | 287 | + } |
| | 288 | + toSplit = PropertiesUtil.getProperties().getStringProperty(PREFIX + propertyName); |
| | 289 | + if (toSplit != null) { |
| | 290 | + list.addAll(Arrays.asList(toSplit.split("\\s*,\\s*"))); |
| | 291 | + } |
| | 292 | + list.addAll(permanentList); |
| | 293 | + } |
186 | } | 294 | } |
187 |
| 295 |
|
188 | @Override | 296 | @Override |
|
0 commit comments