CVE-2014-125087 (published 2023-02-19T17:15Z)

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.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-611
cwe_name: Improper Restriction of XML External Entity Reference
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.
extended_description: 
            XML documents optionally contain a Document Type Definition (DTD), which, among other features, enables the definition of XML entities. It is possible to define an entity by providing a substitution string in the form of a URI. The XML parser can access the contents of this URI and embed these contents back into the XML document for further processing.
            By submitting an XML file that defines an external entity with a file:// URI, an attacker can cause the processing application to read the contents of a local file. For example, a URI such as "file:///c:/winnt/win.ini" designates (in Windows) the file C:\Winnt\win.ini, or file:///etc/passwd designates the password file in Unix-based systems. Using URIs with other schemes such as http://, the attacker can force the application to make outgoing requests to servers that the attacker cannot reach directly, which can be used to bypass firewall restrictions or hide the source of attacks such as port scanning.
            Once the content of the URI is read, it is fed back into the application that is processing the XML. This application may echo back the data (e.g. in an error message), thereby exposing the file contents.
         
         

Fix information:
repo_url: https://github.com/jmurty/java-xmlbuilder
commit_details: repo_url: https://github.com/jmurty/java-xmlbuilder
author: James Murty
author_date: 2014-07-22 21:47:48+01:00
msg: 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.
repository: repo_url: https://github.com/jmurty/java-xmlbuilder
repo_name: jmurty/java-xmlbuilder
description: XML Builder is a utility that allows simple XML documents to be constructed using relatively sparse Java code
repo_language: Java
owner: jmurty
file_changes: filename: CHANGES.md
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,17 @@
 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
 --------------------------
 

programming_language: Markdown
filename: BaseXMLBuilder.java
change_type: ModificationType.MODIFY
diff: @@ -76,6 +76,13 @@ public abstract class BaseXMLBuilder {
 
     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.
@@ -112,6 +119,78 @@ public abstract class BaseXMLBuilder {
         }
     }
 
+    /**
+     * 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.
@@ -126,11 +205,13 @@ public abstract class BaseXMLBuilder {
      * @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;
@@ -157,11 +238,13 @@ public abstract class BaseXMLBuilder {
      * @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;

programming_language: Java
filename: XMLBuilder.java
change_type: ModificationType.MODIFY
diff: @@ -89,6 +89,53 @@ public final class XMLBuilder extends BaseXMLBuilder {
         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
@@ -108,7 +155,7 @@ public final class XMLBuilder extends BaseXMLBuilder {
     public static XMLBuilder create(String name, String namespaceURI)
         throws ParserConfigurationException, FactoryConfigurationError
     {
-        return new XMLBuilder(createDocumentImpl(name, namespaceURI));
+        return create(name, namespaceURI, false);
     }
 
     /**
@@ -130,6 +177,84 @@ public final class XMLBuilder extends BaseXMLBuilder {
         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
@@ -149,7 +274,7 @@ public final class XMLBuilder extends BaseXMLBuilder {
     public static XMLBuilder parse(InputSource inputSource)
         throws ParserConfigurationException, SAXException, IOException
     {
-        return new XMLBuilder(parseDocumentImpl(inputSource));
+        return XMLBuilder.parse(inputSource, false);
     }
 
     /**
@@ -171,7 +296,7 @@ public final class XMLBuilder extends BaseXMLBuilder {
     public static XMLBuilder parse(String xmlString)
         throws ParserConfigurationException, SAXException, IOException
     {
-        return XMLBuilder.parse(new InputSource(new StringReader(xmlString)));
+        return XMLBuilder.parse(xmlString, false);
     }
 
     /**
@@ -193,7 +318,7 @@ public final class XMLBuilder extends BaseXMLBuilder {
     public static XMLBuilder parse(File xmlFile)
         throws ParserConfigurationException, SAXException, IOException
     {
-        return XMLBuilder.parse(new InputSource(new FileReader(xmlFile)));
+        return XMLBuilder.parse(xmlFile, false);
     }
 
     @Override

programming_language: Java
filename: XMLBuilder2.java
change_type: ModificationType.MODIFY
diff: @@ -118,20 +118,63 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      * 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
@@ -146,7 +189,7 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      */
     public static XMLBuilder2 create(String name)
     {
-        return create(name, null);
+        return XMLBuilder2.create(name, null, false);
     }
 
     /**
@@ -156,16 +199,20 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      *
      * @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) {
@@ -182,12 +229,17 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      *
      * @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);
     }
 
     /**
@@ -197,21 +249,76 @@ public final class XMLBuilder2 extends BaseXMLBuilder {
      *
      * @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}

programming_language: Java
filename: BaseXMLBuilderTests.java
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,7 @@ 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;
@@ -52,6 +53,14 @@ public abstract class BaseXMLBuilderTests extends TestCase {
             "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);
@@ -619,4 +628,29 @@ public abstract class BaseXMLBuilderTests extends TestCase {
         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);
+    }
+
 }

programming_language: Java
filename: external.txt
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+Injected XXE Data
\ No newline at end of file

programming_language: SQL