|
17 | 17 |
|
18 | 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 | 28 | import java.util.Properties;
|
21 | 29 | import java.util.concurrent.TimeUnit;
|
22 | 30 |
|
23 | 31 | import javax.naming.Context;
|
24 |
| -import javax.naming.InitialContext; |
| 32 | +import javax.naming.NamingEnumeration; |
25 | 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 | 39 | import org.apache.logging.log4j.core.appender.AbstractManager;
|
28 | 40 | import org.apache.logging.log4j.core.appender.ManagerFactory;
|
29 | 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 | 48 | * @since 2.1
|
35 | 49 | */
|
36 | 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 | 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 | 78 | super(null, name);
|
44 | 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 | 206 | * @throws NamingException if a naming exception is encountered
|
169 | 207 | */
|
170 | 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 | 257 | return (T) this.context.lookup(name);
|
173 | 258 | }
|
174 | 259 |
|
175 | 260 | private static class JndiManagerFactory implements ManagerFactory<JndiManager, Properties> {
|
176 | 261 |
|
177 | 262 | @Override
|
178 | 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 | 273 | try {
|
180 |
| - return new JndiManager(name, new InitialContext(data)); |
| 274 | + return new JndiManager(name, new InitialDirContext(data), allowedHosts, allowedClasses, |
| 275 | + allowedProtocols); |
181 | 276 | } catch (final NamingException e) {
|
182 | 277 | LOGGER.error("Error creating JNDI InitialContext.", e);
|
183 | 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 | 296 | @Override
|
|
0 commit comments