This is an automated email from the ASF dual-hosted git repository.
ppkarwasz pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/2.x by this push:
new 4ce9212cd3 resolve external entities through ConfigurationSource in
log4j 1.x XmlConfiguration (#4198)
4ce9212cd3 is described below
commit 4ce9212cd3ac5e1ebf4b0fdfe08dda3f09ee9a6b
Author: jmestwa-coder <[email protected]>
AuthorDate: Sun Aug 30 14:13:17 2026 +0530
resolve external entities through ConfigurationSource in log4j 1.x
XmlConfiguration (#4198)
* disable external entity resolution in log4j 1.x XmlConfiguration
* constrain external resolution to the bundled log4j.dtd in
Log4jEntityResolver
* resolve external entities through ConfigurationSource in
Log4jEntityResolver
---
.../org/apache/log4j/xml/Log4jEntityResolver.java | 28 +++++-
.../config/XmlConfigurationExternalEntityTest.java | 99 ++++++++++++++++++++++
.../.2.x.x/fix_log4j1_xml_configuration_xxe.xml | 12 +++
3 files changed, 136 insertions(+), 3 deletions(-)
diff --git
a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java
b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java
index 1533fc8938..6eafb999cc 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java
@@ -18,7 +18,10 @@ package org.apache.log4j.xml;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Constants;
import org.xml.sax.EntityResolver;
@@ -27,7 +30,11 @@ import org.xml.sax.InputSource;
/**
* An {@link EntityResolver} specifically designed to return
* <code>log4j.dtd</code> which is embedded within the log4j jar
- * file.
+ * file. Any other external resource is resolved through
+ * {@link ConfigurationSource#fromUri(URI)}, the same way the configuration
+ * file itself is resolved, so it is subject to the same restrictions (e.g.
+ * the {@code log4j2.Configuration.allowedProtocols} property). Resources
+ * that cannot be resolved this way are replaced by an empty source.
*/
public class Log4jEntityResolver implements EntityResolver {
private static final Logger LOGGER = StatusLogger.getLogger();
@@ -35,7 +42,7 @@ public class Log4jEntityResolver implements EntityResolver {
@Override
public InputSource resolveEntity(final String publicId, final String
systemId) {
- if (systemId.endsWith("log4j.dtd") || PUBLIC_ID.equals(publicId)) {
+ if ((systemId != null && systemId.endsWith("log4j.dtd")) ||
PUBLIC_ID.equals(publicId)) {
final Class<?> clazz = getClass();
InputStream in =
clazz.getResourceAsStream("/org/apache/log4j/xml/log4j.dtd");
if (in == null) {
@@ -46,6 +53,21 @@ public class Log4jEntityResolver implements EntityResolver {
}
return new InputSource(in);
}
- return null;
+ // Resolve using `ConfigurationSource`
+ if (systemId != null) {
+ try {
+ final ConfigurationSource source =
ConfigurationSource.fromUri(new URI(systemId));
+ if (source != null) {
+ final InputSource inputSource = new
InputSource(source.getInputStream());
+ inputSource.setSystemId(systemId);
+ return inputSource;
+ }
+ } catch (final URISyntaxException e) {
+ LOGGER.warn("Resolution error: invalid URI {}", systemId, e);
+ }
+ }
+ // Fall back to empty resource
+ LOGGER.warn("Resolution error: unable to resolve {}", systemId);
+ return new InputSource(new
ByteArrayInputStream(Constants.EMPTY_BYTE_ARRAY));
}
}
diff --git
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationExternalEntityTest.java
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationExternalEntityTest.java
new file mode 100644
index 0000000000..6b137bef0b
--- /dev/null
+++
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationExternalEntityTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.log4j.config;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.test.junit.SetTestProperty;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Checks that the Log4j 1.x XML configuration reader resolves external
entities through
+ * {@link ConfigurationSource}, so they are subject to the {@code
log4j2.Configuration.allowedProtocols} restrictions.
+ */
+class XmlConfigurationExternalEntityTest {
+
+ private static final String INJECTED_LOGGER = "external-entity-injected";
+
+ /**
+ * A {@code file} entity is allowed by default and is still resolved.
+ */
+ @Test
+ void resolvesAllowedExternalEntities(@TempDir final Path tempDir) throws
Exception {
+ final Path injected = tempDir.resolve("injected.xml");
+ Files.write(injected, ("<logger name=\"" + INJECTED_LOGGER +
"\"></logger>").getBytes(StandardCharsets.UTF_8));
+
+ final Configuration configuration = configure(tempDir,
injected.toUri().toString());
+
+ assertTrue(
+ configuration.getLoggers().containsKey(INJECTED_LOGGER),
+ "External entity was not resolved; allowed protocols must
still be resolved");
+ }
+
+ /**
+ * An entity fetched over a protocol that is not allowed is replaced by an
empty source. The {@code @Timeout}
+ * guards against the resolver reaching the network instead of rejecting
the protocol.
+ */
+ @Test
+ @Timeout(5)
+ @SetTestProperty(key = "log4j2.configurationAllowedProtocols", value =
"file")
+ void ignoresDisallowedExternalEntities(@TempDir final Path tempDir) throws
Exception {
+ final Configuration configuration = configure(tempDir,
"http://localhost:1/injected.xml");
+
+ assertFalse(
+ configuration.getLoggers().containsKey(INJECTED_LOGGER),
+ "External entity was resolved; disallowed protocols must not
be resolved");
+ }
+
+ private static Configuration configure(final Path tempDir, final String
entitySystemId) throws Exception {
+ // If the external entity is resolved, its replacement text injects a
logger into the configuration.
+ final Path configFile = tempDir.resolve("log4j1-external-entity.xml");
+ final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\" [\n"
+ + " <!ENTITY injected SYSTEM \"" + entitySystemId + "\">\n"
+ + "]>\n"
+ + "<log4j:configuration
xmlns:log4j=\"http://jakarta.apache.org/log4j/\">\n"
+ + " <appender name=\"console\"
class=\"org.apache.log4j.ConsoleAppender\">\n"
+ + " <layout class=\"org.apache.log4j.SimpleLayout\"/>\n"
+ + " </appender>\n"
+ + " &injected;\n"
+ + " <root>\n"
+ + " <priority value=\"debug\"/>\n"
+ + " <appender-ref ref=\"console\"/>\n"
+ + " </root>\n"
+ + "</log4j:configuration>\n";
+ Files.write(configFile, xml.getBytes(StandardCharsets.UTF_8));
+
+ final ConfigurationSource source = new
ConfigurationSource(Files.newInputStream(configFile), configFile);
+ final LoggerContext context = LoggerContext.getContext(false);
+ final Configuration configuration = new
XmlConfigurationFactory().getConfiguration(context, source);
+ assertNotNull(configuration, "No configuration created");
+ configuration.initialize();
+ return configuration;
+ }
+}
diff --git a/src/changelog/.2.x.x/fix_log4j1_xml_configuration_xxe.xml
b/src/changelog/.2.x.x/fix_log4j1_xml_configuration_xxe.xml
new file mode 100644
index 0000000000..d2bd5d1d12
--- /dev/null
+++ b/src/changelog/.2.x.x/fix_log4j1_xml_configuration_xxe.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns="https://logging.apache.org/xml/ns"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ https://logging.apache.org/xml/ns
+ https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="fixed">
+ <issue id="4198" link="https://github.com/apache/logging-log4j2/pull/4198"/>
+ <description format="asciidoc">
+ Resolve external entities in the Log4j 1.x `XmlConfiguration` reader
through `ConfigurationSource`, so they are subject to the
`log4j2.Configuration.allowedProtocols` restrictions
+ </description>
+</entry>