CVE-2022-0265: Improper Restriction of XML External Entity Reference (CWE-611)
value: Improper Restriction of XML External Entity Reference in GitHub repository hazelcast/hazelcast in 5.1-BETA-1.
CWE description: The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output.
CVSS v3 base score: 9.8
Published: 2022-03-03T22:15Z
Repository: https://github.com/hazelcast/hazelcast
Language: Java
Files changed: hazelcast/src/main/java/com/hazelcast/internal/config/AbstractXmlConfigRootTagRecognizer.java, hazelcast/src/main/java/com/hazelcast/internal/util/XmlUtil.java, hazelcast/src/test/java/com/hazelcast/internal/util/XmlUtilTest.java
Commit date: 2022-01-19 09:37:38 +0100
Commit message: Add helper method to XmlUtil to enable XXE protection in the SAXParserFactory and XMLInputFactory (#20407)


Diff: diff --git a/hazelcast/src/main/java/com/hazelcast/internal/config/AbstractXmlConfigRootTagRecognizer.java b/hazelcast/src/main/java/com/hazelcast/internal/config/AbstractXmlConfigRootTagRecognizer.java
index eeb7cf9492..92ade5f12a 100644
--- a/hazelcast/src/main/java/com/hazelcast/internal/config/AbstractXmlConfigRootTagRecognizer.java
+++ b/hazelcast/src/main/java/com/hazelcast/internal/config/AbstractXmlConfigRootTagRecognizer.java
@@ -16,10 +16,11 @@
 
 package com.hazelcast.internal.config;
 
 import com.hazelcast.config.ConfigRecognizer;
 import com.hazelcast.config.ConfigStream;
+import com.hazelcast.internal.util.XmlUtil;
 import com.hazelcast.logging.ILogger;
 import com.hazelcast.logging.Logger;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.SAXParseException;
@@ -52,11 +53,11 @@ public class AbstractXmlConfigRootTagRecognizer implements ConfigRecognizer {
     private final String expectedRootNode;
     private final ILogger logger = Logger.getLogger(AbstractXmlConfigRootTagRecognizer.class);
 
     public AbstractXmlConfigRootTagRecognizer(String expectedRootNode) throws Exception {
         this.expectedRootNode = expectedRootNode;
-        SAXParserFactory factory = SAXParserFactory.newInstance();
+        SAXParserFactory factory = XmlUtil.getSAXParserFactory();
         saxParser = factory.newSAXParser();
     }
 
     @Override
     public boolean isRecognized(ConfigStream configStream) throws Exception {
diff --git a/hazelcast/src/main/java/com/hazelcast/internal/util/XmlUtil.java b/hazelcast/src/main/java/com/hazelcast/internal/util/XmlUtil.java
index 556cb213cf..1d23a7d5f0 100644
--- a/hazelcast/src/main/java/com/hazelcast/internal/util/XmlUtil.java
+++ b/hazelcast/src/main/java/com/hazelcast/internal/util/XmlUtil.java
@@ -23,10 +23,12 @@ import java.io.StringWriter;
 
 import javax.annotation.Nullable;
 import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.stream.XMLInputFactory;
 import javax.xml.transform.ErrorListener;
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Source;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
@@ -39,11 +41,14 @@ import org.xml.sax.SAXException;
 
 import com.hazelcast.logging.ILogger;
 import com.hazelcast.logging.Logger;
 
 /**
- * Utility class for XML processing.
+ * Utility class for XML processing. It contains several methods to retrieve XML processing factories with XXE protection
+ * enabled (based on recommendation in the
+ * <a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html">OWASP XXE prevention
+ * cheat-sheet</a>).
  */
 public final class XmlUtil {
 
     /**
      * System property name which allows ignoring failures during enabling the XML External Entity protection.
@@ -53,10 +58,11 @@ public final class XmlUtil {
      * 8+). Old JAXP implementations on the classpath (e.g. Xerces, Xalan) may miss the support and they throw exception
      * during enabling the XXE protection. Setting this system property to true suppresses/ignores such Exceptions.
      */
     public static final String SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES = "hazelcast.ignoreXxeProtectionFailures";
 
+    private static final String FEATURES_DISALLOW_DOCTYPE = "http://apache.org/xml/features/disallow-doctype-decl";
     private static final ILogger LOGGER = Logger.getLogger(XmlUtil.class);
 
     private XmlUtil() {
     }
 
@@ -66,11 +72,11 @@ public final class XmlUtil {
      * @throws ParserConfigurationException enabling XXE protection fail
      */
     public static DocumentBuilderFactory getNsAwareDocumentBuilderFactory() throws ParserConfigurationException {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         dbf.setNamespaceAware(true);
-        setFeature(dbf, "http://apache.org/xml/features/disallow-doctype-decl");
+        setFeature(dbf, FEATURES_DISALLOW_DOCTYPE);
         return dbf;
     }
 
     /**
      * Returns {@link TransformerFactory} with XXE protection enabled.
@@ -90,10 +96,28 @@ public final class XmlUtil {
         setProperty(schemaFactory, XMLConstants.ACCESS_EXTERNAL_SCHEMA);
         setProperty(schemaFactory, XMLConstants.ACCESS_EXTERNAL_DTD);
         return schemaFactory;
     }
 
+    /**
+     * Returns {@link SAXParserFactory} with XXE protection enabled.
+     */
+    public static SAXParserFactory getSAXParserFactory() throws ParserConfigurationException, SAXException {
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        setFeature(factory, FEATURES_DISALLOW_DOCTYPE);
+        return factory;
+    }
+
+    /**
+     * Returns {@link XMLInputFactory} with XXE protection enabled.
+     */
+    public static XMLInputFactory getXMLInputFactory() {
+        XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
+        setProperty(xmlInputFactory, XMLInputFactory.SUPPORT_DTD, false);
+        return xmlInputFactory;
+    }
+
     /**
      * Formats given XML String with the given indentation used. If the {@code input} XML string is {@code null}, or
      * {@code indent} parameter is negative, or XML transformation fails, then the original value is returned unchanged. The
      * {@link IllegalArgumentException} is thrown when {@code indent==0}.
      *
@@ -164,66 +188,67 @@ public final class XmlUtil {
 
     static void setAttribute(TransformerFactory transformerFactory, String attributeName) {
         try {
             transformerFactory.setAttribute(attributeName, "");
         } catch (IllegalArgumentException iae) {
-            if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
-                LOGGER.warning("Enabling XXE protection failed. The attribute " + attributeName
-                        + " is not supported by the TransformerFactory. The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
-                        + " system property is used so the XML processing continues in the UNSECURE mode"
-                        + " with XXE protection disabled!!!");
-            } else {
-                LOGGER.severe("Enabling XXE protection failed. The attribute " + attributeName
-                        + " is not supported by the TransformerFactory. This usually mean an outdated XML processor"
-                        + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
-                        + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
-                        + " system property can be used to disable XML External Entity protections."
-                        + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!!!", iae);
-                throw iae;
-            }
+            printWarningAndRethrowEventually(iae, TransformerFactory.class, "attribute " + attributeName);
         }
     }
 
     static void setFeature(DocumentBuilderFactory dbf, String featureName) throws ParserConfigurationException {
         try {
             dbf.setFeature(featureName, true);
         } catch (ParserConfigurationException e) {
-            if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
-                LOGGER.warning("Enabling XXE protection failed. The feature " + featureName
-                        + " is not supported by the DocumentBuilderFactory. The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
-                        + " system property is used so the XML processing continues in the UNSECURE mode"
-                        + " with XXE protection disabled!!!");
-            } else {
-                LOGGER.severe("Enabling XXE protection failed. The feature " + featureName
-                        + " is not supported by the DocumentBuilderFactory. This usually mean an outdated XML processor"
-                        + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
-                        + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
-                        + " system property can be used to disable XML External Entity protections."
-                        + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!!!", e);
-                throw e;
-            }
+            printWarningAndRethrowEventually(e, DocumentBuilderFactory.class, "feature " + featureName);
+        }
+    }
+
+    static void setFeature(SAXParserFactory saxParserFactory, String featureName)
+            throws ParserConfigurationException, SAXException {
+        try {
+            saxParserFactory.setFeature(featureName, true);
+        } catch (SAXException e) {
+            printWarningAndRethrowEventually(e, SAXParserFactory.class, "feature " + featureName);
+        } catch (ParserConfigurationException e) {
+            printWarningAndRethrowEventually(e, SAXParserFactory.class, "feature " + featureName);
         }
     }
 
     static void setProperty(SchemaFactory schemaFactory, String propertyName) throws SAXException {
         try {
             schemaFactory.setProperty(propertyName, "");
         } catch (SAXException e) {
-            if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
-                LOGGER.warning("Enabling XXE protection failed. The property " + propertyName
-                        + " is not supported by the SchemaFactory. The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
-                        + " system property is used so the XML processing continues in the UNSECURE mode"
-                        + " with XXE protection disabled!!!");
-            } else {
-                LOGGER.severe("Enabling XXE protection failed. The property " + propertyName
-                        + " is not supported by the SchemaFactory. This usually mean an outdated XML processor"
-                        + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
-                        + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
-                        + " system property can be used to disable XML External Entity protections."
-                        + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!!!", e);
-                throw e;
-            }
+            printWarningAndRethrowEventually(e, SchemaFactory.class, "property " + propertyName);
+        }
+    }
+
+    static void setProperty(XMLInputFactory xmlInputFactory, String propertyName, Object value) {
+        try {
+            xmlInputFactory.setProperty(propertyName, value);
+        } catch (IllegalArgumentException e) {
+            printWarningAndRethrowEventually(e, XMLInputFactory.class, "property " + propertyName);
+        }
+    }
+
+    private static <T extends Exception> void printWarningAndRethrowEventually(T cause, Class<?> clazz, String objective)
+            throws T {
+        String className = clazz.getSimpleName();
+        if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
+            LOGGER.warning("Enabling XXE protection failed. The " + objective + " is not supported by the " + className
+                    + ". The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
+                    + " system property is used so the XML processing continues in the UNSECURE mode"
+                    + " with XXE protection disabled!!!");
+        } else {
+            LOGGER.severe(
+                    "Enabling XXE protection failed. The " + objective + " is not supported by the " + className
+                            + ". This usually mean an outdated XML processor"
+                            + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
+                            + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
+                            + " system property can be used to disable XML External Entity protections."
+                            + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!",
+                    cause);
+            throw cause;
         }
     }
 
     /**
      * ErrorListener implementation which just throws the error. It workarounds IBM Java default behaviour when
diff --git a/hazelcast/src/test/java/com/hazelcast/internal/util/XmlUtilTest.java b/hazelcast/src/test/java/com/hazelcast/internal/util/XmlUtilTest.java
index 351ff457d3..1502490f81 100644
--- a/hazelcast/src/test/java/com/hazelcast/internal/util/XmlUtilTest.java
+++ b/hazelcast/src/test/java/com/hazelcast/internal/util/XmlUtilTest.java
@@ -16,23 +16,43 @@
 
 package com.hazelcast.internal.util;
 
 import static com.hazelcast.internal.util.XmlUtil.SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES;
 import static com.hazelcast.internal.util.XmlUtil.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.validation.SchemaFactory;
 
+import org.fusesource.hawtbuf.ByteArrayInputStream;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
+import org.xml.sax.HandlerBase;
 import org.xml.sax.SAXException;
 
 import com.hazelcast.test.HazelcastSerialClassRunner;
 import com.hazelcast.test.OverridePropertyRule;
 import com.hazelcast.test.annotation.QuickTest;
@@ -43,22 +63,46 @@ public class XmlUtilTest {
 
     @Rule
     public OverridePropertyRule ignoreXxeFailureProp = OverridePropertyRule
             .clear(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES);
 
+    private DummyServer server;
+
+    @Before
+    public void before() throws IOException {
+        server = new DummyServer();
+        server.start();
+    }
+
+    @After
+    public void after() {
+        server.stop();
+    }
+
+    @Test
+    public void testUnprotectedXxe() throws Exception {
+        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        try {
+            db.parse(new ByteArrayInputStream(server.getTestXml().getBytes(UTF_8)));
+        } catch (Exception e) {
+            // not important if it fails
+        }
+        assertThat(server.getHits(), Matchers.greaterThan(0));
+    }
+
     @Test
     public void testFormat() throws Exception {
         assertEquals("<a> <b>c</b></a>", format("<a><b>c</b></a>", 1).replaceAll("[\r\n]", ""));
         assertEquals("<a>   <b>c</b></a>", format("<a><b>c</b></a>", 3).replaceAll("[\r\n]", ""));
         assertEquals("<a><b>c</b></a>", format("<a><b>c</b></a>", -21));
 
         assertThrows(IllegalArgumentException.class, () -> format("<a><b>c</b></a>", 0));
 
         // check if the XXE protection is enabled
-        String xxeAttack = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "  <!DOCTYPE test [\n"
-                + "    <!ENTITY xxe SYSTEM \"file:///etc/passwd\">\n" + "  ]>" + "<a><b>&xxe;</b></a>";
-        assertEquals(xxeAttack, format(xxeAttack, 1));
+        String xml = server.getTestXml();
+        assertEquals(xml, format(xml, 1));
+        assertEquals(0, server.getHits());
 
         // wrongly formatted XML
         assertEquals("<a><b>c</b><a>", format("<a><b>c</b><a>", 1));
     }
 
@@ -92,6 +136,101 @@ public class XmlUtilTest {
         ignoreXxeFailureProp.setOrClearProperty("false");
         assertThrows(ParserConfigurationException.class, () -> XmlUtil.setFeature(dbf, "test://no-such-feature"));
         ignoreXxeFailureProp.setOrClearProperty("true");
         XmlUtil.setFeature(dbf, "test://no-such-feature");
     }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testGetSAXParserFactory() throws Exception {
+        SAXParserFactory saxParserFactory = XmlUtil.getSAXParserFactory();
+        assertNotNull(saxParserFactory);
+        // check if the XXE protection is enabled
+        SAXParser saxParser = saxParserFactory.newSAXParser();
+        assertThrows(SAXException.class,
+                () -> saxParser.parse(new ByteArrayInputStream(server.getTestXml().getBytes(UTF_8)), new HandlerBase()));
+        assertEquals(0, server.getHits());
+
+        assertThrows(SAXException.class, () -> XmlUtil.setFeature(saxParserFactory, "test://no-such-feature"));
+        ignoreXxeFailureProp.setOrClearProperty("false");
+        assertThrows(SAXException.class, () -> XmlUtil.setFeature(saxParserFactory, "test://no-such-feature"));
+        ignoreXxeFailureProp.setOrClearProperty("true");
+        XmlUtil.setFeature(saxParserFactory, "test://no-such-feature");
+    }
+
+    @Test
+    public void testGetXmlInputFactory() throws Exception {
+        XMLInputFactory xmlInputFactory = XmlUtil.getXMLInputFactory();
+        assertNotNull(xmlInputFactory);
+        // check if the XXE protection is enabled
+        assertThrows(XMLStreamException.class,
+                () -> staxReadEvents(xmlInputFactory.createXMLEventReader(new StringReader(server.getTestXml()))));
+        assertEquals(0, server.getHits());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> XmlUtil.setProperty(xmlInputFactory, "test://no-such-property", false));
+        ignoreXxeFailureProp.setOrClearProperty("false");
+        assertThrows(IllegalArgumentException.class,
+                () -> XmlUtil.setProperty(xmlInputFactory, "test://no-such-property", false));
+        ignoreXxeFailureProp.setOrClearProperty("true");
+        XmlUtil.setProperty(xmlInputFactory, "test://no-such-feature", false);
+    }
+
+    private void staxReadEvents(XMLEventReader reader) throws XMLStreamException {
+        try {
+            while (reader.hasNext()) {
+                reader.nextEvent();
+            }
+        } finally {
+            reader.close();
+        }
+    }
+
+    static class DummyServer implements Runnable {
+        private static final String XXE_TEST_STR_TEMPLATE = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "  <!DOCTYPE test [\n" + "    <!ENTITY xxe SYSTEM \"%s\">\n" + "  ]>" + "<a><b>&xxe;</b></a>";
+
+        private final ServerSocket serverSocket;
+        private final AtomicInteger counter = new AtomicInteger();
+
+        DummyServer() throws IOException {
+            serverSocket = new ServerSocket(0, 5, InetAddress.getLoopbackAddress());
+        }
+
+        public void start() {
+            new Thread(this, "DummyServer-acceptor").start();
+        }
+
+        public String getUrlString() {
+            return "http://127.0.0.1:" + serverSocket.getLocalPort();
+        }
+
+        public String getTestXml() {
+            return String.format(XXE_TEST_STR_TEMPLATE, getUrlString());
+        }
+
+        public int getHits() {
+            return counter.get();
+        }
+
+        public void stop() {
+            try {
+                serverSocket.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void run() {
+            while (true) {
+                try (Socket socket = serverSocket.accept()) {
+                    System.out.println(">> connection accepted: " + socket);
+                    counter.incrementAndGet();
+                } catch (Exception e) {
+                    System.out.println(">> stopping the server. Cause: " + e.getClass().getName());
+                    return;
+                }
+            }
+        }
+    }
 }

Vulnerable code:         SAXParserFactory factory = SAXParserFactory.newInstance();
 * Utility class for XML processing.
        setFeature(dbf, "http://apache.org/xml/features/disallow-doctype-decl");
            if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
                LOGGER.warning("Enabling XXE protection failed. The attribute " + attributeName
                        + " is not supported by the TransformerFactory. The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                        + " system property is used so the XML processing continues in the UNSECURE mode"
                        + " with XXE protection disabled!!!");
            } else {
                LOGGER.severe("Enabling XXE protection failed. The attribute " + attributeName
                        + " is not supported by the TransformerFactory. This usually mean an outdated XML processor"
                        + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
                        + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                        + " system property can be used to disable XML External Entity protections."
                        + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!!!", iae);
                throw iae;
            }
            if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
                LOGGER.warning("Enabling XXE protection failed. The feature " + featureName
                        + " is not supported by the DocumentBuilderFactory. The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                        + " system property is used so the XML processing continues in the UNSECURE mode"
                        + " with XXE protection disabled!!!");
            } else {
                LOGGER.severe("Enabling XXE protection failed. The feature " + featureName
                        + " is not supported by the DocumentBuilderFactory. This usually mean an outdated XML processor"
                        + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
                        + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                        + " system property can be used to disable XML External Entity protections."
                        + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!!!", e);
                throw e;
            }
            if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
                LOGGER.warning("Enabling XXE protection failed. The property " + propertyName
                        + " is not supported by the SchemaFactory. The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                        + " system property is used so the XML processing continues in the UNSECURE mode"
                        + " with XXE protection disabled!!!");
            } else {
                LOGGER.severe("Enabling XXE protection failed. The property " + propertyName
                        + " is not supported by the SchemaFactory. This usually mean an outdated XML processor"
                        + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
                        + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                        + " system property can be used to disable XML External Entity protections."
                        + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!!!", e);
                throw e;
            }
        String xxeAttack = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "  <!DOCTYPE test [\n"
                + "    <!ENTITY xxe SYSTEM \"file:///etc/passwd\">\n" + "  ]>" + "<a><b>&xxe;</b></a>";
        assertEquals(xxeAttack, format(xxeAttack, 1));
Fixed code: import com.hazelcast.internal.util.XmlUtil;
        SAXParserFactory factory = XmlUtil.getSAXParserFactory();
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLInputFactory;
 * Utility class for XML processing. It contains several methods to retrieve XML processing factories with XXE protection
 * enabled (based on recommendation in the
 * <a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html">OWASP XXE prevention
 * cheat-sheet</a>).
    private static final String FEATURES_DISALLOW_DOCTYPE = "http://apache.org/xml/features/disallow-doctype-decl";
        setFeature(dbf, FEATURES_DISALLOW_DOCTYPE);
    /**
     * Returns {@link SAXParserFactory} with XXE protection enabled.
     */
    public static SAXParserFactory getSAXParserFactory() throws ParserConfigurationException, SAXException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        setFeature(factory, FEATURES_DISALLOW_DOCTYPE);
        return factory;
    }

    /**
     * Returns {@link XMLInputFactory} with XXE protection enabled.
     */
    public static XMLInputFactory getXMLInputFactory() {
        XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
        setProperty(xmlInputFactory, XMLInputFactory.SUPPORT_DTD, false);
        return xmlInputFactory;
    }

            printWarningAndRethrowEventually(iae, TransformerFactory.class, "attribute " + attributeName);
            printWarningAndRethrowEventually(e, DocumentBuilderFactory.class, "feature " + featureName);
        }
    }

    static void setFeature(SAXParserFactory saxParserFactory, String featureName)
            throws ParserConfigurationException, SAXException {
        try {
            saxParserFactory.setFeature(featureName, true);
        } catch (SAXException e) {
            printWarningAndRethrowEventually(e, SAXParserFactory.class, "feature " + featureName);
        } catch (ParserConfigurationException e) {
            printWarningAndRethrowEventually(e, SAXParserFactory.class, "feature " + featureName);
            printWarningAndRethrowEventually(e, SchemaFactory.class, "property " + propertyName);
        }
    }

    static void setProperty(XMLInputFactory xmlInputFactory, String propertyName, Object value) {
        try {
            xmlInputFactory.setProperty(propertyName, value);
        } catch (IllegalArgumentException e) {
            printWarningAndRethrowEventually(e, XMLInputFactory.class, "property " + propertyName);
        }
    }

    private static <T extends Exception> void printWarningAndRethrowEventually(T cause, Class<?> clazz, String objective)
            throws T {
        String className = clazz.getSimpleName();
        if (Boolean.getBoolean(SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES)) {
            LOGGER.warning("Enabling XXE protection failed. The " + objective + " is not supported by the " + className
                    + ". The " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                    + " system property is used so the XML processing continues in the UNSECURE mode"
                    + " with XXE protection disabled!!!");
        } else {
            LOGGER.severe(
                    "Enabling XXE protection failed. The " + objective + " is not supported by the " + className
                            + ". This usually mean an outdated XML processor"
                            + " is present on the classpath (e.g. Xerces, Xalan). If you are not able to resolve the issue by"
                            + " fixing the classpath, the " + SYSTEM_PROPERTY_IGNORE_XXE_PROTECTION_FAILURES
                            + " system property can be used to disable XML External Entity protections."
                            + " We don't recommend disabling the XXE as such the XML processor configuration is unsecure!",
                    cause);
            throw cause;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.IOException;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicInteger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import org.fusesource.hawtbuf.ByteArrayInputStream;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.xml.sax.HandlerBase;
    private DummyServer server;

    @Before
    public void before() throws IOException {
        server = new DummyServer();
        server.start();
    }

    @After
    public void after() {
        server.stop();
    }

    @Test
    public void testUnprotectedXxe() throws Exception {
        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        try {
            db.parse(new ByteArrayInputStream(server.getTestXml().getBytes(UTF_8)));
        } catch (Exception e) {
            // not important if it fails
        }
        assertThat(server.getHits(), Matchers.greaterThan(0));
    }

        String xml = server.getTestXml();
        assertEquals(xml, format(xml, 1));
        assertEquals(0, server.getHits());

    @SuppressWarnings("deprecation")
    @Test
    public void testGetSAXParserFactory() throws Exception {
        SAXParserFactory saxParserFactory = XmlUtil.getSAXParserFactory();
        assertNotNull(saxParserFactory);
        // check if the XXE protection is enabled
        SAXParser saxParser = saxParserFactory.newSAXParser();
        assertThrows(SAXException.class,
                () -> saxParser.parse(new ByteArrayInputStream(server.getTestXml().getBytes(UTF_8)), new HandlerBase()));
        assertEquals(0, server.getHits());

        assertThrows(SAXException.class, () -> XmlUtil.setFeature(saxParserFactory, "test://no-such-feature"));
        ignoreXxeFailureProp.setOrClearProperty("false");
        assertThrows(SAXException.class, () -> XmlUtil.setFeature(saxParserFactory, "test://no-such-feature"));
        ignoreXxeFailureProp.setOrClearProperty("true");
        XmlUtil.setFeature(saxParserFactory, "test://no-such-feature");
    }

    @Test
    public void testGetXmlInputFactory() throws Exception {
        XMLInputFactory xmlInputFactory = XmlUtil.getXMLInputFactory();
        assertNotNull(xmlInputFactory);
        // check if the XXE protection is enabled
        assertThrows(XMLStreamException.class,
                () -> staxReadEvents(xmlInputFactory.createXMLEventReader(new StringReader(server.getTestXml()))));
        assertEquals(0, server.getHits());

        assertThrows(IllegalArgumentException.class,
                () -> XmlUtil.setProperty(xmlInputFactory, "test://no-such-property", false));
        ignoreXxeFailureProp.setOrClearProperty("false");
        assertThrows(IllegalArgumentException.class,
                () -> XmlUtil.setProperty(xmlInputFactory, "test://no-such-property", false));
        ignoreXxeFailureProp.setOrClearProperty("true");
        XmlUtil.setProperty(xmlInputFactory, "test://no-such-feature", false);
    }

    private void staxReadEvents(XMLEventReader reader) throws XMLStreamException {
        try {
            while (reader.hasNext()) {
                reader.nextEvent();
            }
        } finally {
            reader.close();
        }
    }

    static class DummyServer implements Runnable {
        private static final String XXE_TEST_STR_TEMPLATE = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "  <!DOCTYPE test [\n" + "    <!ENTITY xxe SYSTEM \"%s\">\n" + "  ]>" + "<a><b>&xxe;</b></a>";

        private final ServerSocket serverSocket;
        private final AtomicInteger counter = new AtomicInteger();

        DummyServer() throws IOException {
            serverSocket = new ServerSocket(0, 5, InetAddress.getLoopbackAddress());
        }

        public void start() {
            new Thread(this, "DummyServer-acceptor").start();
        }

        public String getUrlString() {
            return "http://127.0.0.1:" + serverSocket.getLocalPort();
        }

        public String getTestXml() {
            return String.format(XXE_TEST_STR_TEMPLATE, getUrlString());
        }

        public int getHits() {
            return counter.get();
        }

        public void stop() {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            while (true) {
                try (Socket socket = serverSocket.accept()) {
                    System.out.println(">> connection accepted: " + socket);
                    counter.incrementAndGet();
                } catch (Exception e) {
                    System.out.println(">> stopping the server. Cause: " + e.getClass().getName());
                    return;
                }
            }
        }
    }