CVE-2014-125087: Improper Restriction of XML External Entity Reference (CWE-611)
value: A vulnerability was found in java-xmlbuilder up to 1.1. It has been rated as problematic. Affected by this issue is some unknown functionality. The manipulation leads to xml external entity reference. Upgrading to version 1.2 is able to address this issue. The name of the patch is e6fddca201790abab4f2c274341c0bb8835c3e73. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-221480.
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: 2023-02-19T17:15Z
Repository: https://github.com/jmurty/java-xmlbuilder
Language: Java
Files changed: CHANGES.md, src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java, src/main/java/com/jamesmurty/utils/XMLBuilder.java, src/main/java/com/jamesmurty/utils/XMLBuilder2.java, src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java, src/test/java/com/jamesmurty/utils/external.txt
Commit date: 2014-07-22 21:51:25 +0100
Commit message: Disable external entities by default to prevent XXE injection attacks, re #6

XML Builder classes now explicitly enable or disable
'external-general-entities' and 'external-parameter-entities' features
of the DocumentBuilderFactory when #create or #parse methods are used.

To prevent XML External Entity (XXE) injection attacks, these features
are disabled by default. They can only be enabled by passing a true
boolean value to new versions of the #create and #parse methods that
accept a flag for this feature.

Diff: diff --git a/CHANGES.md b/CHANGES.md
index dfa7694..5154782 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,8 +1,19 @@
 Release Notes for java-xmlbuilder
 =================================
 
+Version 1.2 - Pending
+---------------------
+
+Fixes:
+
+* Prevent XML External Entity (XXE) injection attacks by disabling parsing of
+  general and parameter external entities by default (#6). External entities
+  are now only parsed if this feature is explicitly enabled by passing a boolean
+  flag value to the #create and #parse methods.  
+  WARNING: This will break code that expects external entities to be parsed.
+
 Version 1.1 - 22 July 2014
 --------------------------
 
 Added a new `XMLBuilder2` implementation that avoids checked exceptions in the
 API and throws runtime exceptions instead. This should make the library much
diff --git a/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java b/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java
index da71b9f..90e26d5 100644
--- a/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java
+++ b/src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java
@@ -74,10 +74,17 @@ public abstract class BaseXMLBuilder {
      */
     private Node xmlNode = null;
 
     private static boolean isNamespaceAware = true;
 
+    /**
+     * If true, the builder will raise an {@link XMLBuilderRuntimeException}
+     * if external general and parameter entities cannot be explicitly enabled
+     * or disabled.
+     */
+    public static boolean failIfExternalEntityParsingCannotBeConfigured = true;
+
     /**
      * Construct a new builder object that wraps the given XML document.
      * This constructor is for internal use only.
      *
      * @param xmlDocument
@@ -110,10 +117,82 @@ public abstract class BaseXMLBuilder {
         if (parentNode != null) {
             parentNode.appendChild(myNode);
         }
     }
 
+    /**
+     * Explicitly enable or disable the 'external-general-entities' and
+     * 'external-parameter-entities' features of the underlying
+     * DocumentBuilderFactory.
+     *
+     * TODO This is a naive approach that simply tries to apply all known
+     * feature name/URL values in turn until one succeeds, or none do.
+     *
+     * @param factory
+     * factory which will have external general and parameter entities enabled
+     * or disabled.
+     * @param enableExternalEntities
+     * if true external entities will be explicitly enabled, otherwise they
+     * will be explicitly disabled.
+     */
+    protected static void enableOrDisableExternalEntityParsing(
+        DocumentBuilderFactory factory, boolean enableExternalEntities)
+    {
+        // Feature list drawn from:
+        // https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
+
+        /* Enable or disable external general entities */
+        String[] externalGeneralEntitiesFeatures = {
+            // General
+            "http://xml.org/sax/features/external-general-entities",
+            // Xerces 1
+            "http://xerces.apache.org/xerces-j/features.html#external-general-entities",
+            // Xerces 2
+            "http://xerces.apache.org/xerces2-j/features.html#external-general-entities",
+        };
+        boolean success = false;
+        for (String feature: externalGeneralEntitiesFeatures) {
+            try {
+                factory.setFeature(feature, enableExternalEntities);
+                success = true;
+                break;
+            } catch (ParserConfigurationException e) {
+            }
+        }
+        if (!success && failIfExternalEntityParsingCannotBeConfigured) {
+            throw new XMLBuilderRuntimeException(
+                new ParserConfigurationException(
+                    "Failed to set 'external-general-entities' feature to "
+                    + enableExternalEntities));
+        }
+
+        /* Enable or disable external parameter entities */
+        String[] externalParameterEntitiesFeatures = {
+            // General
+            "http://xml.org/sax/features/external-parameter-entities",
+            // Xerces 1
+            "http://xerces.apache.org/xerces-j/features.html#external-parameter-entities",
+            // Xerces 2
+            "http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities",
+        };
+        success = false;
+        for (String feature: externalParameterEntitiesFeatures) {
+            try {
+                factory.setFeature(feature, enableExternalEntities);
+                success = true;
+                break;
+            } catch (ParserConfigurationException e) {
+            }
+        }
+        if (!success && failIfExternalEntityParsingCannotBeConfigured) {
+            throw new XMLBuilderRuntimeException(
+                new ParserConfigurationException(
+                    "Failed to set 'external-parameter-entities' feature to "
+                    + enableExternalEntities));
+        }
+    }
+
     /**
      * Construct an XML Document with a default namespace with the given
      * root element.
      *
      * @param name
@@ -124,15 +203,17 @@ public abstract class BaseXMLBuilder {
      * an XML Document.
      *
      * @throws FactoryConfigurationError
      * @throws ParserConfigurationException
      */
-    protected static Document createDocumentImpl(String name, String namespaceURI)
+    protected static Document createDocumentImpl(
+        String name, String namespaceURI, boolean enableExternalEntities)
         throws ParserConfigurationException, FactoryConfigurationError
     {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(isNamespaceAware);
+        enableOrDisableExternalEntityParsing(factory, enableExternalEntities);
         DocumentBuilder builder = factory.newDocumentBuilder();
         Document document = builder.newDocument();
         Element rootElement = null;
         if (namespaceURI != null && namespaceURI.length() > 0) {
             rootElement = document.createElementNS(namespaceURI, name);
@@ -155,15 +236,17 @@ public abstract class BaseXMLBuilder {
      * @throws FactoryConfigurationError
      * @throws ParserConfigurationException
      * @throws IOException
      * @throws SAXException
      */
-    protected static Document parseDocumentImpl(InputSource inputSource)
+    protected static Document parseDocumentImpl(
+        InputSource inputSource, boolean enableExternalEntities)
         throws ParserConfigurationException, SAXException, IOException
     {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(isNamespaceAware);
+        enableOrDisableExternalEntityParsing(factory, enableExternalEntities);
         DocumentBuilder builder = factory.newDocumentBuilder();
         Document document = builder.parse(inputSource);
         return document;
     }
 
diff --git a/src/main/java/com/jamesmurty/utils/XMLBuilder.java b/src/main/java/com/jamesmurty/utils/XMLBuilder.java
index 9b1144a..5663b44 100644
--- a/src/main/java/com/jamesmurty/utils/XMLBuilder.java
+++ b/src/main/java/com/jamesmurty/utils/XMLBuilder.java
@@ -87,10 +87,57 @@ public final class XMLBuilder extends BaseXMLBuilder {
      */
     protected XMLBuilder(Node myNode, Node parentNode) {
         super(myNode, parentNode);
     }
 
+    /**
+     * Construct a builder for new XML document with a default namespace.
+     * The document will be created with the given root element, and the builder
+     * returned by this method will serve as the starting-point for any further
+     * document additions.
+     *
+     * @param name
+     * the name of the document's root element.
+     * @param namespaceURI
+     * default namespace URI for document, ignored if null or empty.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     *
+     * @throws FactoryConfigurationError
+     * @throws ParserConfigurationException
+     */
+    public static XMLBuilder create(String name, String namespaceURI,
+        boolean enableExternalEntities)
+        throws ParserConfigurationException, FactoryConfigurationError
+    {
+        return new XMLBuilder(
+            createDocumentImpl(name, namespaceURI, enableExternalEntities));
+    }
+
+    /**
+     * Construct a builder for new XML document. The document will be created
+     * with the given root element, and the builder returned by this method
+     * will serve as the starting-point for any further document additions.
+     *
+     * @param name
+     * the name of the document's root element.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     *
+     * @throws FactoryConfigurationError
+     * @throws ParserConfigurationException
+     */
+    public static XMLBuilder create(String name, boolean enableExternalEntities)
+        throws ParserConfigurationException, FactoryConfigurationError
+    {
+        return create(name, null, enableExternalEntities);
+    }
+
     /**
      * Construct a builder for new XML document with a default namespace.
      * The document will be created with the given root element, and the builder
      * returned by this method will serve as the starting-point for any further
      * document additions.
@@ -106,11 +153,11 @@ public final class XMLBuilder extends BaseXMLBuilder {
      * @throws ParserConfigurationException
      */
     public static XMLBuilder create(String name, String namespaceURI)
         throws ParserConfigurationException, FactoryConfigurationError
     {
-        return new XMLBuilder(createDocumentImpl(name, namespaceURI));
+        return create(name, namespaceURI, false);
     }
 
     /**
      * Construct a builder for new XML document. The document will be created
      * with the given root element, and the builder returned by this method
@@ -128,10 +175,88 @@ public final class XMLBuilder extends BaseXMLBuilder {
         throws ParserConfigurationException, FactoryConfigurationError
     {
         return create(name, null);
     }
 
+    /**
+     * Construct a builder from an existing XML document. The provided XML
+     * document will be parsed and an XMLBuilder object referencing the
+     * document's root element will be returned.
+     *
+     * @param inputSource
+     * an XML document input source that will be parsed into a DOM.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     * @throws ParserConfigurationException
+     *
+     * @throws FactoryConfigurationError
+     * @throws ParserConfigurationException
+     * @throws IOException
+     * @throws SAXException
+     */
+    public static XMLBuilder parse(
+        InputSource inputSource, boolean enableExternalEntities)
+        throws ParserConfigurationException, SAXException, IOException
+    {
+        return new XMLBuilder(
+            parseDocumentImpl(inputSource, enableExternalEntities));
+    }
+
+    /**
+     * Construct a builder from an existing XML document string.
+     * The provided XML document will be parsed and an XMLBuilder
+     * object referencing the document's root element will be returned.
+     *
+     * @param xmlString
+     * an XML document string that will be parsed into a DOM.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     *
+     * @throws ParserConfigurationException
+     * @throws FactoryConfigurationError
+     * @throws ParserConfigurationException
+     * @throws IOException
+     * @throws SAXException
+     */
+    public static XMLBuilder parse(
+        String xmlString, boolean enableExternalEntities)
+        throws ParserConfigurationException, SAXException, IOException
+    {
+        return XMLBuilder.parse(
+            new InputSource(new StringReader(xmlString)),
+            enableExternalEntities);
+    }
+
+    /**
+     * Construct a builder from an existing XML document file.
+     * The provided XML document will be parsed and an XMLBuilder
+     * object referencing the document's root element will be returned.
+     *
+     * @param xmlFile
+     * an XML document file that will be parsed into a DOM.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     *
+     * @throws ParserConfigurationException
+     * @throws FactoryConfigurationError
+     * @throws ParserConfigurationException
+     * @throws IOException
+     * @throws SAXException
+     */
+    public static XMLBuilder parse(File xmlFile, boolean enableExternalEntities)
+        throws ParserConfigurationException, SAXException, IOException
+    {
+        return XMLBuilder.parse(
+            new InputSource(new FileReader(xmlFile)), enableExternalEntities);
+    }
+
     /**
      * Construct a builder from an existing XML document. The provided XML
      * document will be parsed and an XMLBuilder object referencing the
      * document's root element will be returned.
      *
@@ -147,11 +272,11 @@ public final class XMLBuilder extends BaseXMLBuilder {
      * @throws SAXException
      */
     public static XMLBuilder parse(InputSource inputSource)
         throws ParserConfigurationException, SAXException, IOException
     {
-        return new XMLBuilder(parseDocumentImpl(inputSource));
+        return XMLBuilder.parse(inputSource, false);
     }
 
     /**
      * Construct a builder from an existing XML document string.
      * The provided XML document will be parsed and an XMLBuilder
@@ -169,11 +294,11 @@ public final class XMLBuilder extends BaseXMLBuilder {
      * @throws SAXException
      */
     public static XMLBuilder parse(String xmlString)
         throws ParserConfigurationException, SAXException, IOException
     {
-        return XMLBuilder.parse(new InputSource(new StringReader(xmlString)));
+        return XMLBuilder.parse(xmlString, false);
     }
 
     /**
      * Construct a builder from an existing XML document file.
      * The provided XML document will be parsed and an XMLBuilder
@@ -191,11 +316,11 @@ public final class XMLBuilder extends BaseXMLBuilder {
      * @throws SAXException
      */
     public static XMLBuilder parse(File xmlFile)
         throws ParserConfigurationException, SAXException, IOException
     {
-        return XMLBuilder.parse(new InputSource(new FileReader(xmlFile)));
+        return XMLBuilder.parse(xmlFile, false);
     }
 
     @Override
     public XMLBuilder stripWhitespaceOnlyTextNodes()
         throws XPathExpressionException
diff --git a/src/main/java/com/jamesmurty/utils/XMLBuilder2.java b/src/main/java/com/jamesmurty/utils/XMLBuilder2.java
index 61ea8af..013ad7c 100644
--- a/src/main/java/com/jamesmurty/utils/XMLBuilder2.java
+++ b/src/main/java/com/jamesmurty/utils/XMLBuilder2.java
@@ -116,24 +116,67 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      *
      * @param name
      * the name of the document's root element.
      * @param namespaceURI
      * default namespace URI for document, ignored if null or empty.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
      * @return
      * a builder node that can be used to add more nodes to the XML document.
      * @throws XMLBuilderRuntimeException
      * to wrap {@link ParserConfigurationException}
      */
-    public static XMLBuilder2 create(String name, String namespaceURI)
+    public static XMLBuilder2 create(
+        String name, String namespaceURI, boolean enableExternalEntities)
     {
         try {
-            return new XMLBuilder2(createDocumentImpl(name, namespaceURI));
+            return new XMLBuilder2(
+                createDocumentImpl(name, namespaceURI, enableExternalEntities));
         } catch (ParserConfigurationException e) {
             throw wrapExceptionAsRuntimeException(e);
         }
     }
 
+    /**
+     * Construct a builder for new XML document. The document will be created
+     * with the given root element, and the builder returned by this method
+     * will serve as the starting-point for any further document additions.
+     *
+     * @param name
+     * the name of the document's root element.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     * @throws XMLBuilderRuntimeException
+     * to wrap {@link ParserConfigurationException}
+     */
+    public static XMLBuilder2 create(String name, boolean enableExternalEntities)
+    {
+        return XMLBuilder2.create(name, null, enableExternalEntities);
+    }
+
+    /**
+     * Construct a builder for new XML document with a default namespace.
+     * The document will be created with the given root element, and the builder
+     * returned by this method will serve as the starting-point for any further
+     * document additions.
+     *
+     * @param name
+     * the name of the document's root element.
+     * @param namespaceURI
+     * default namespace URI for document, ignored if null or empty.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     * @throws XMLBuilderRuntimeException
+     * to wrap {@link ParserConfigurationException}
+     */
+    public static XMLBuilder2 create(String name, String namespaceURI)
+    {
+        return XMLBuilder2.create(name, namespaceURI, false);
+    }
+
     /**
      * Construct a builder for new XML document. The document will be created
      * with the given root element, and the builder returned by this method
      * will serve as the starting-point for any further document additions.
      *
@@ -144,30 +187,34 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      * @throws XMLBuilderRuntimeException
      * to wrap {@link ParserConfigurationException}
      */
     public static XMLBuilder2 create(String name)
     {
-        return create(name, null);
+        return XMLBuilder2.create(name, null, false);
     }
 
     /**
      * Construct a builder from an existing XML document. The provided XML
      * document will be parsed and an XMLBuilder2 object referencing the
      * document's root element will be returned.
      *
      * @param inputSource
      * an XML document input source that will be parsed into a DOM.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
      * @return
      * a builder node that can be used to add more nodes to the XML document.
      * @throws XMLBuilderRuntimeException
      * to wrap {@link ParserConfigurationException}, {@link SAXException},
      * {@link IOException}
      */
-    public static XMLBuilder2 parse(InputSource inputSource)
+    public static XMLBuilder2 parse(
+        InputSource inputSource, boolean enableExternalEntities)
     {
         try {
-            return new XMLBuilder2(parseDocumentImpl(inputSource));
+            return new XMLBuilder2(
+                parseDocumentImpl(inputSource, enableExternalEntities));
         } catch (ParserConfigurationException e) {
             throw wrapExceptionAsRuntimeException(e);
         } catch (SAXException e) {
             throw wrapExceptionAsRuntimeException(e);
         } catch (IOException e) {
@@ -180,40 +227,100 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      * The provided XML document will be parsed and an XMLBuilder2
      * object referencing the document's root element will be returned.
      *
      * @param xmlString
      * an XML document string that will be parsed into a DOM.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
      * @return
      * a builder node that can be used to add more nodes to the XML document.
      */
-    public static XMLBuilder2 parse(String xmlString)
+    public static XMLBuilder2 parse(
+        String xmlString, boolean enableExternalEntities)
     {
-        return XMLBuilder2.parse(new InputSource(new StringReader(xmlString)));
+        return XMLBuilder2.parse(
+            new InputSource(new StringReader(xmlString)),
+            enableExternalEntities);
     }
 
     /**
      * Construct a builder from an existing XML document file.
      * The provided XML document will be parsed and an XMLBuilder2
      * object referencing the document's root element will be returned.
      *
      * @param xmlFile
      * an XML document file that will be parsed into a DOM.
+     * @param enableExternalEntities
+     * enable external entities; beware of XML External Entity (XXE) injection.
      * @return
      * a builder node that can be used to add more nodes to the XML document.
      * @throws XMLBuilderRuntimeException
      * to wrap {@link ParserConfigurationException}, {@link SAXException},
      * {@link IOException}, {@link FileNotFoundException}
      */
-    public static XMLBuilder2 parse(File xmlFile)
+    public static XMLBuilder2 parse(File xmlFile, boolean enableExternalEntities)
     {
         try {
-            return XMLBuilder2.parse(new InputSource(new FileReader(xmlFile)));
+            return XMLBuilder2.parse(
+                new InputSource(new FileReader(xmlFile)),
+                enableExternalEntities);
         } catch (FileNotFoundException e) {
             throw wrapExceptionAsRuntimeException(e);
         }
     }
 
+    /**
+     * Construct a builder from an existing XML document. The provided XML
+     * document will be parsed and an XMLBuilder2 object referencing the
+     * document's root element will be returned.
+     *
+     * @param inputSource
+     * an XML document input source that will be parsed into a DOM.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     * @throws XMLBuilderRuntimeException
+     * to wrap {@link ParserConfigurationException}, {@link SAXException},
+     * {@link IOException}
+     */
+    public static XMLBuilder2 parse(InputSource inputSource)
+    {
+        return XMLBuilder2.parse(inputSource, false);
+    }
+
+    /**
+     * Construct a builder from an existing XML document string.
+     * The provided XML document will be parsed and an XMLBuilder2
+     * object referencing the document's root element will be returned.
+     *
+     * @param xmlString
+     * an XML document string that will be parsed into a DOM.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     */
+    public static XMLBuilder2 parse(String xmlString)
+    {
+        return XMLBuilder2.parse(xmlString, false);
+    }
+
+    /**
+     * Construct a builder from an existing XML document file.
+     * The provided XML document will be parsed and an XMLBuilder2
+     * object referencing the document's root element will be returned.
+     *
+     * @param xmlFile
+     * an XML document file that will be parsed into a DOM.
+     * @return
+     * a builder node that can be used to add more nodes to the XML document.
+     * @throws XMLBuilderRuntimeException
+     * to wrap {@link ParserConfigurationException}, {@link SAXException},
+     * {@link IOException}, {@link FileNotFoundException}
+     */
+    public static XMLBuilder2 parse(File xmlFile)
+    {
+        return XMLBuilder2.parse(xmlFile, false);
+    }
+
     /**
      * @throws XMLBuilderRuntimeException
      * to wrap {@link XPathExpressionException}
      */
     @Override
diff --git a/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java b/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java
index e488307..54959fc 100644
--- a/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java
+++ b/src/test/java/com/jamesmurty/utils/BaseXMLBuilderTests.java
@@ -4,10 +4,11 @@ import java.io.File;
 import java.io.FileWriter;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.Properties;
 
+import javax.annotation.Resource;
 import javax.xml.transform.OutputKeys;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
 
 import junit.framework.TestCase;
@@ -50,10 +51,18 @@ public abstract class BaseXMLBuilderTests extends TestCase {
     protected BaseXMLBuilder XMLBuilder_parse(InputSource source) throws Exception {
         return (BaseXMLBuilder) XMLBuilderToTest().getMethod(
             "parse", InputSource.class).invoke(null, source);
     }
 
+    protected BaseXMLBuilder XMLBuilder_parse(
+        String documentString, boolean enableExternalEntities) throws Exception
+    {
+        return (BaseXMLBuilder) XMLBuilderToTest().getMethod(
+            "parse", String.class, boolean.class).invoke(
+                null, documentString, enableExternalEntities);
+    }
+
     protected BaseXMLBuilder XMLBuilder_parse(String documentString) throws Exception {
         return (BaseXMLBuilder) XMLBuilderToTest().getMethod(
             "parse", String.class).invoke(null, documentString);
     }
 
@@ -617,6 +626,31 @@ public abstract class BaseXMLBuilderTests extends TestCase {
         builder.toWriter(new FileWriter(f), outputProperties);
         assertEquals(expectedByteSize, f.length());
         f.delete();
     }
 
+    /**
+     * Ensure XML Builder parse methods use a default configuration that
+     * prevents XML External Entity (XXE) injection attacks.
+     *
+     * @throws Exception
+     */
+    public void testXMLBuilderParserImmuneToXXEAttackByDefault() throws Exception {
+        String externalFilePath = "src/test/java/com/jamesmurty/utils/external.txt";
+        File externalFile = new File(externalFilePath);
+        String XML_DOC_WITH_XXE =
+            "<?xml version=\"1.0\"?>" +
+            "<!DOCTYPE Projects [" +
+                "<!ELEMENT JetS3t ANY>" +
+                "<!ENTITY xx1 SYSTEM \"" + externalFile.toURI() + "\"> ]>" +
+            EXAMPLE_XML_DOC_START + "&xx1;" + EXAMPLE_XML_DOC_END;
+        // By default, builder is immune from XXE injection
+        BaseXMLBuilder builder = XMLBuilder_parse(XML_DOC_WITH_XXE);
+        String parsedXml = builder.asString();
+        assertFalse(parsedXml.indexOf("Injected XXE Data") >= 0);
+        // If you enable external entity processing, builder becomes subject to XXE injection
+        builder = XMLBuilder_parse(XML_DOC_WITH_XXE, true);
+        parsedXml = builder.asString();
+        assertTrue(parsedXml.indexOf("Injected XXE Data") >= 0);
+    }
+
 }
diff --git a/src/test/java/com/jamesmurty/utils/external.txt b/src/test/java/com/jamesmurty/utils/external.txt
new file mode 100644
index 0000000..c067079
--- /dev/null
+++ b/src/test/java/com/jamesmurty/utils/external.txt
@@ -0,0 +1 @@
+Injected XXE Data
\ No newline at end of file

Vulnerable code:     protected static Document createDocumentImpl(String name, String namespaceURI)
    protected static Document parseDocumentImpl(InputSource inputSource)
        return new XMLBuilder(createDocumentImpl(name, namespaceURI));
        return new XMLBuilder(parseDocumentImpl(inputSource));
        return XMLBuilder.parse(new InputSource(new StringReader(xmlString)));
        return XMLBuilder.parse(new InputSource(new FileReader(xmlFile)));
    public static XMLBuilder2 create(String name, String namespaceURI)
            return new XMLBuilder2(createDocumentImpl(name, namespaceURI));
        return create(name, null);
    public static XMLBuilder2 parse(InputSource inputSource)
            return new XMLBuilder2(parseDocumentImpl(inputSource));
    public static XMLBuilder2 parse(String xmlString)
        return XMLBuilder2.parse(new InputSource(new StringReader(xmlString)));
    public static XMLBuilder2 parse(File xmlFile)
            return XMLBuilder2.parse(new InputSource(new FileReader(xmlFile)));
Fixed code: Version 1.2 - Pending
---------------------

Fixes:

* Prevent XML External Entity (XXE) injection attacks by disabling parsing of
  general and parameter external entities by default (#6). External entities
  are now only parsed if this feature is explicitly enabled by passing a boolean
  flag value to the #create and #parse methods.  
  WARNING: This will break code that expects external entities to be parsed.

    /**
     * If true, the builder will raise an {@link XMLBuilderRuntimeException}
     * if external general and parameter entities cannot be explicitly enabled
     * or disabled.
     */
    public static boolean failIfExternalEntityParsingCannotBeConfigured = true;

    /**
     * Explicitly enable or disable the 'external-general-entities' and
     * 'external-parameter-entities' features of the underlying
     * DocumentBuilderFactory.
     *
     * TODO This is a naive approach that simply tries to apply all known
     * feature name/URL values in turn until one succeeds, or none do.
     *
     * @param factory
     * factory which will have external general and parameter entities enabled
     * or disabled.
     * @param enableExternalEntities
     * if true external entities will be explicitly enabled, otherwise they
     * will be explicitly disabled.
     */
    protected static void enableOrDisableExternalEntityParsing(
        DocumentBuilderFactory factory, boolean enableExternalEntities)
    {
        // Feature list drawn from:
        // https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing

        /* Enable or disable external general entities */
        String[] externalGeneralEntitiesFeatures = {
            // General
            "http://xml.org/sax/features/external-general-entities",
            // Xerces 1
            "http://xerces.apache.org/xerces-j/features.html#external-general-entities",
            // Xerces 2
            "http://xerces.apache.org/xerces2-j/features.html#external-general-entities",
        };
        boolean success = false;
        for (String feature: externalGeneralEntitiesFeatures) {
            try {
                factory.setFeature(feature, enableExternalEntities);
                success = true;
                break;
            } catch (ParserConfigurationException e) {
            }
        }
        if (!success && failIfExternalEntityParsingCannotBeConfigured) {
            throw new XMLBuilderRuntimeException(
                new ParserConfigurationException(
                    "Failed to set 'external-general-entities' feature to "
                    + enableExternalEntities));
        }

        /* Enable or disable external parameter entities */
        String[] externalParameterEntitiesFeatures = {
            // General
            "http://xml.org/sax/features/external-parameter-entities",
            // Xerces 1
            "http://xerces.apache.org/xerces-j/features.html#external-parameter-entities",
            // Xerces 2
            "http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities",
        };
        success = false;
        for (String feature: externalParameterEntitiesFeatures) {
            try {
                factory.setFeature(feature, enableExternalEntities);
                success = true;
                break;
            } catch (ParserConfigurationException e) {
            }
        }
        if (!success && failIfExternalEntityParsingCannotBeConfigured) {
            throw new XMLBuilderRuntimeException(
                new ParserConfigurationException(
                    "Failed to set 'external-parameter-entities' feature to "
                    + enableExternalEntities));
        }
    }

    protected static Document createDocumentImpl(
        String name, String namespaceURI, boolean enableExternalEntities)
        enableOrDisableExternalEntityParsing(factory, enableExternalEntities);
    protected static Document parseDocumentImpl(
        InputSource inputSource, boolean enableExternalEntities)
        enableOrDisableExternalEntityParsing(factory, enableExternalEntities);
    /**
     * Construct a builder for new XML document with a default namespace.
     * The document will be created with the given root element, and the builder
     * returned by this method will serve as the starting-point for any further
     * document additions.
     *
     * @param name
     * the name of the document's root element.
     * @param namespaceURI
     * default namespace URI for document, ignored if null or empty.
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     *
     * @throws FactoryConfigurationError
     * @throws ParserConfigurationException
     */
    public static XMLBuilder create(String name, String namespaceURI,
        boolean enableExternalEntities)
        throws ParserConfigurationException, FactoryConfigurationError
    {
        return new XMLBuilder(
            createDocumentImpl(name, namespaceURI, enableExternalEntities));
    }

    /**
     * Construct a builder for new XML document. The document will be created
     * with the given root element, and the builder returned by this method
     * will serve as the starting-point for any further document additions.
     *
     * @param name
     * the name of the document's root element.
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     *
     * @throws FactoryConfigurationError
     * @throws ParserConfigurationException
     */
    public static XMLBuilder create(String name, boolean enableExternalEntities)
        throws ParserConfigurationException, FactoryConfigurationError
    {
        return create(name, null, enableExternalEntities);
    }

        return create(name, namespaceURI, false);
    /**
     * Construct a builder from an existing XML document. The provided XML
     * document will be parsed and an XMLBuilder object referencing the
     * document's root element will be returned.
     *
     * @param inputSource
     * an XML document input source that will be parsed into a DOM.
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     * @throws ParserConfigurationException
     *
     * @throws FactoryConfigurationError
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static XMLBuilder parse(
        InputSource inputSource, boolean enableExternalEntities)
        throws ParserConfigurationException, SAXException, IOException
    {
        return new XMLBuilder(
            parseDocumentImpl(inputSource, enableExternalEntities));
    }

    /**
     * Construct a builder from an existing XML document string.
     * The provided XML document will be parsed and an XMLBuilder
     * object referencing the document's root element will be returned.
     *
     * @param xmlString
     * an XML document string that will be parsed into a DOM.
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     *
     * @throws ParserConfigurationException
     * @throws FactoryConfigurationError
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static XMLBuilder parse(
        String xmlString, boolean enableExternalEntities)
        throws ParserConfigurationException, SAXException, IOException
    {
        return XMLBuilder.parse(
            new InputSource(new StringReader(xmlString)),
            enableExternalEntities);
    }

    /**
     * Construct a builder from an existing XML document file.
     * The provided XML document will be parsed and an XMLBuilder
     * object referencing the document's root element will be returned.
     *
     * @param xmlFile
     * an XML document file that will be parsed into a DOM.
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     *
     * @throws ParserConfigurationException
     * @throws FactoryConfigurationError
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static XMLBuilder parse(File xmlFile, boolean enableExternalEntities)
        throws ParserConfigurationException, SAXException, IOException
    {
        return XMLBuilder.parse(
            new InputSource(new FileReader(xmlFile)), enableExternalEntities);
    }

        return XMLBuilder.parse(inputSource, false);
        return XMLBuilder.parse(xmlString, false);
        return XMLBuilder.parse(xmlFile, false);
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
    public static XMLBuilder2 create(
        String name, String namespaceURI, boolean enableExternalEntities)
            return new XMLBuilder2(
                createDocumentImpl(name, namespaceURI, enableExternalEntities));
    /**
     * Construct a builder for new XML document. The document will be created
     * with the given root element, and the builder returned by this method
     * will serve as the starting-point for any further document additions.
     *
     * @param name
     * the name of the document's root element.
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     * @throws XMLBuilderRuntimeException
     * to wrap {@link ParserConfigurationException}
     */
    public static XMLBuilder2 create(String name, boolean enableExternalEntities)
    {
        return XMLBuilder2.create(name, null, enableExternalEntities);
    }

    /**
     * Construct a builder for new XML document with a default namespace.
     * The document will be created with the given root element, and the builder
     * returned by this method will serve as the starting-point for any further
     * document additions.
     *
     * @param name
     * the name of the document's root element.
     * @param namespaceURI
     * default namespace URI for document, ignored if null or empty.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     * @throws XMLBuilderRuntimeException
     * to wrap {@link ParserConfigurationException}
     */
    public static XMLBuilder2 create(String name, String namespaceURI)
    {
        return XMLBuilder2.create(name, namespaceURI, false);
    }

        return XMLBuilder2.create(name, null, false);
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
    public static XMLBuilder2 parse(
        InputSource inputSource, boolean enableExternalEntities)
            return new XMLBuilder2(
                parseDocumentImpl(inputSource, enableExternalEntities));
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
    public static XMLBuilder2 parse(
        String xmlString, boolean enableExternalEntities)
        return XMLBuilder2.parse(
            new InputSource(new StringReader(xmlString)),
            enableExternalEntities);
     * @param enableExternalEntities
     * enable external entities; beware of XML External Entity (XXE) injection.
    public static XMLBuilder2 parse(File xmlFile, boolean enableExternalEntities)
            return XMLBuilder2.parse(
                new InputSource(new FileReader(xmlFile)),
                enableExternalEntities);
    /**
     * Construct a builder from an existing XML document. The provided XML
     * document will be parsed and an XMLBuilder2 object referencing the
     * document's root element will be returned.
     *
     * @param inputSource
     * an XML document input source that will be parsed into a DOM.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     * @throws XMLBuilderRuntimeException
     * to wrap {@link ParserConfigurationException}, {@link SAXException},
     * {@link IOException}
     */
    public static XMLBuilder2 parse(InputSource inputSource)
    {
        return XMLBuilder2.parse(inputSource, false);
    }

    /**
     * Construct a builder from an existing XML document string.
     * The provided XML document will be parsed and an XMLBuilder2
     * object referencing the document's root element will be returned.
     *
     * @param xmlString
     * an XML document string that will be parsed into a DOM.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     */
    public static XMLBuilder2 parse(String xmlString)
    {
        return XMLBuilder2.parse(xmlString, false);
    }

    /**
     * Construct a builder from an existing XML document file.
     * The provided XML document will be parsed and an XMLBuilder2
     * object referencing the document's root element will be returned.
     *
     * @param xmlFile
     * an XML document file that will be parsed into a DOM.
     * @return
     * a builder node that can be used to add more nodes to the XML document.
     * @throws XMLBuilderRuntimeException
     * to wrap {@link ParserConfigurationException}, {@link SAXException},
     * {@link IOException}, {@link FileNotFoundException}
     */
    public static XMLBuilder2 parse(File xmlFile)
    {
        return XMLBuilder2.parse(xmlFile, false);
    }

import javax.annotation.Resource;
    protected BaseXMLBuilder XMLBuilder_parse(
        String documentString, boolean enableExternalEntities) throws Exception
    {
        return (BaseXMLBuilder) XMLBuilderToTest().getMethod(
            "parse", String.class, boolean.class).invoke(
                null, documentString, enableExternalEntities);
    }

    /**
     * Ensure XML Builder parse methods use a default configuration that
     * prevents XML External Entity (XXE) injection attacks.
     *
     * @throws Exception
     */
    public void testXMLBuilderParserImmuneToXXEAttackByDefault() throws Exception {
        String externalFilePath = "src/test/java/com/jamesmurty/utils/external.txt";
        File externalFile = new File(externalFilePath);
        String XML_DOC_WITH_XXE =
            "<?xml version=\"1.0\"?>" +
            "<!DOCTYPE Projects [" +
                "<!ELEMENT JetS3t ANY>" +
                "<!ENTITY xx1 SYSTEM \"" + externalFile.toURI() + "\"> ]>" +
            EXAMPLE_XML_DOC_START + "&xx1;" + EXAMPLE_XML_DOC_END;
        // By default, builder is immune from XXE injection
        BaseXMLBuilder builder = XMLBuilder_parse(XML_DOC_WITH_XXE);
        String parsedXml = builder.asString();
        assertFalse(parsedXml.indexOf("Injected XXE Data") >= 0);
        // If you enable external entity processing, builder becomes subject to XXE injection
        builder = XMLBuilder_parse(XML_DOC_WITH_XXE, true);
        parsedXml = builder.asString();
        assertTrue(parsedXml.indexOf("Injected XXE Data") >= 0);
    }

Injected XXE Data