[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

CVE-2015-1833 (Jackrabbit WebDAV XXE vulnerability)



Dear readers,

we just fixed a recently reported vulnerability in Apache Jackrabbit's WebDAV module; see

- the attached CVE report

- patches for all currently maintained Jackrabbit branches

We just released Jackrabbit 2.10.1 (see below) and we'll get to the other branches shortly. Check the CVE for details about what to do for earlier branches if you can't wait for a release.

Thanks to <0ang3el@xxxxxxxxx> for bringing this to our attention and giving valuable feedback while we investigated the problem.

Thanks and best regards, Julian

-------- Forwarded Message --------
Subject: [ANNOUNCE] Apache Jackrabbit 2.10.1 released
Date: Thu, 21 May 2015 11:22:04 +0200
From: Marcel Reutegger <mreutegg@xxxxxxxxxx>
Reply-To: users@xxxxxxxxxxxxxxxxxxxxx
To: announce@xxxxxxxxxx, announce@xxxxxxxxxxxxxxxxxxxxx, Jackrabbit Developers <dev@xxxxxxxxxxxxxxxxxxxxx>, Jackrabbit Users <users@xxxxxxxxxxxxxxxxxxxxx>, 0ang3el 0ang3el <0ang3el@xxxxxxxxx>, security@xxxxxxxxxx, oss-security@xxxxxxxxxxxxxxxxxx, bugtraq@xxxxxxxxxxxxxxxxx

The Apache Jackrabbit community is pleased to announce the release of
Apache Jackrabbit 2.10.1. This release fixes an important security issue in
the jackrabbit-webdav module reported by Mikhail Egorov.

The release is available for download at:

 http://jackrabbit.apache.org/downloads.html

See the full release notes below for details about this release.

Release Notes -- Apache Jackrabbit -- Version 2.10.1

Introduction
------------

This is Apache Jackrabbit(TM) 2.10.1, a fully compliant implementation of the
Content Repository for Java(TM) Technology API, version 2.0 (JCR 2.0) as
specified in the Java Specification Request 283 (JSR 283).

Apache Jackrabbit 2.10.1 is a patch release that contains fixes and
improvements over Jackrabbit 2.10. Jackrabbit 2.10.x releases are considered
stable and targeted for production use.

Security advisory (JCR-3883 / CVE-2015-1833)
--------------------------------------------

This release fixes an important security issue in the jackrabbit-webdav module
reported by Mikhail Egorov.

When processing a WebDAV request body containing XML, the XML parser can be
instructed to read content from network resources accessible to the host,
identified by URI schemes such as "http(s)" or  "file". Depending on the
WebDAV request, this can not only be used to trigger internal network
requests, but might also be used to insert said content into the request,
potentially exposing it to the attacker and others (for instance, by inserting
said content in a WebDAV property value using a PROPPATCH request). See also
IETF RFC 4918, Section 20.6.

Users of the jackrabbit-webdav module are advised to immediately update the
module to this release or disable WebDAV access to the repository. Users
on earlier versions of Jackrabbit who are unable to upgrade to 2.10.1 should
apply the fix to the corresponding 2.x branch or disable WebDAV access until
official releases of those earlier versions are available. Patches for 2.x
branches are attached to the JIRA issue.

Changes since Jackrabbit 2.10.0
-------------------------------

Bug fixes

  [JCR-3853] JCR2SPI: Load ac provider resource
  [JCR-3871] POI Vulnerabilities
  [JCR-3872] Config DTD does not declare ProtectedItemImporter elements
  [JCR-3873] CachingDataStore not safe against crashes, corrupted
uploads file will prevent system startup
  [JCR-3876] POM dependency to jackrabbit-data test-jar is not test-scoped
  [JCR-3878] Fix test case failure in jackrabbit-data
  [JCR-3883] Jackrabbit WebDAV bundle susceptible to XXE/XEE attack

Improvements

  [JCR-3864] CachingDatastore -cache file sizes to save remote call to
remote datastore( S3DS)
  [JCR-3868] Adapt TestCaseBase.java to test for FileDatastore
  [JCR-3869] CachingDataStore for SAN or NFS mounted storage
  [JCR-3879] Remove contention in AsyncUploadCache to improve performance
  [JCR-3881] Change CachingFDS configuration properties

New Features

  [JCR-3836] Allow to get an Authorizable of a given type

Sub-tasks

  [JCR-3837] Add AuthorizableTypeException in user security API package

In addition to the above-mentioned changes, this release contains
all the changes included up to the Apache Jackrabbit 2.10.0 release.

For more detailed information about all the changes in this and other
Jackrabbit releases, please see the Jackrabbit issue tracker at

    https://issues.apache.org/jira/browse/JCR

Release Contents
----------------

This release consists of a single source archive packaged as a zip file.
The archive can be unpacked with the jar tool from your JDK installation.
See the README.txt file for instructions on how to build this release.

The source archive is accompanied by SHA1 and MD5 checksums and a PGP
signature that you can use to verify the authenticity of your download.
The public key used for the PGP signature can be found at
https://svn.apache.org/repos/asf/jackrabbit/dist/KEYS.

About Apache Jackrabbit
-----------------------

Apache Jackrabbit is a fully conforming implementation of the Content
Repository for Java Technology API (JCR). A content repository is a
hierarchical content store with support for structured and unstructured
content, full text search, versioning, transactions, observation, and
more.

For more information, visit http://jackrabbit.apache.org/

About The Apache Software Foundation
------------------------------------

Established in 1999, The Apache Software Foundation provides organizational,
legal, and financial support for more than 140 freely-available,
collaboratively-developed Open Source projects. The pragmatic Apache License
enables individual and commercial users to easily deploy Apache software;
the Foundation's intellectual property framework limits the legal exposure
of its 3,800+ contributors.

For more information, visit http://www.apache.org/

Trademarks
----------

Apache Jackrabbit, Jackrabbit, Apache, the Apache feather logo, and the Apache
Jackrabbit project logo are trademarks of The Apache Software Foundation.



Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java
===================================================================
--- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java	(revision 0)
+++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java	(working copy)
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import java.io.IOException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Custom {@link DocumentBuilderFactory} extended for use in WebDAV.
+ */
+public class DavDocumentBuilderFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DomUtil.class);
+
+    private final DocumentBuilderFactory DEFAULT_FACTORY = createFactory();
+
+    private DocumentBuilderFactory BUILDER_FACTORY = DEFAULT_FACTORY;
+
+    private DocumentBuilderFactory createFactory() {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setIgnoringComments(true);
+        factory.setIgnoringElementContentWhitespace(true);
+        factory.setCoalescing(true);
+        try {
+            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        } catch (ParserConfigurationException e) {
+            LOG.warn("Secure XML processing is not supported", e);
+        } catch (AbstractMethodError e) {
+            LOG.warn("Secure XML processing is not supported", e);
+        }
+        return factory;
+    }
+
+    public void setFactory(DocumentBuilderFactory documentBuilderFactory) {
+        LOG.debug("DocumentBuilderFactory changed to: " + documentBuilderFactory);
+        BUILDER_FACTORY = documentBuilderFactory != null ? documentBuilderFactory : DEFAULT_FACTORY;
+    }
+
+    /**
+     * An entity resolver that does not allow external entity resolution. See
+     * RFC 4918, Section 20.6
+     */
+    private static final EntityResolver DEFAULT_ENTITY_RESOLVER = new EntityResolver() {
+        @Override
+        public InputSource resolveEntity(String publicId, String systemId) throws IOException {
+            LOG.debug("Resolution of external entities in XML payload not supported - publicId: " + publicId + ", systemId: "
+                    + systemId);
+            throw new IOException("This parser does not support resolution of external entities (publicId: " + publicId
+                    + ", systemId: " + systemId + ")");
+        }
+    };
+
+    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilder db = BUILDER_FACTORY.newDocumentBuilder();
+        if (BUILDER_FACTORY == DEFAULT_FACTORY) {
+            // if this is the default factory: set the default entity resolver as well
+            db.setEntityResolver(DEFAULT_ENTITY_RESOLVER);
+        }
+        db.setErrorHandler(new DefaultHandler());
+        return db;
+    }
+}
Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
===================================================================
--- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java	(revision 1678308)
+++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java	(working copy)
@@ -28,9 +28,7 @@
 import org.w3c.dom.Text;
 import org.w3c.dom.NamedNodeMap;
 import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
 
-import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -56,27 +54,11 @@
     private static Logger log = LoggerFactory.getLogger(DomUtil.class);
 
     /**
-     * Constant for <code>DocumentBuilderFactory</code> which is used
+     * Constant for <code>DavDocumentBuilderFactory</code> which is used
      * to create and parse DOM documents.
      */
-    private static DocumentBuilderFactory BUILDER_FACTORY = createFactory();
+    private static final DavDocumentBuilderFactory BUILDER_FACTORY = new DavDocumentBuilderFactory();
 
-    private static DocumentBuilderFactory createFactory() {
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        factory.setNamespaceAware(true);
-        factory.setIgnoringComments(true);
-        factory.setIgnoringElementContentWhitespace(true);
-        factory.setCoalescing(true);
-        try {
-            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-        } catch (ParserConfigurationException e) {
-            log.warn("Secure XML processing is not supported", e);
-        } catch (AbstractMethodError e) {
-            log.warn("Secure XML processing is not supported", e);
-        }
-        return factory;
-    }
-
     /**
      * Support the replacement of {@link #BUILDER_FACTORY}. This is useful
      * for injecting a customized BuilderFactory, for example with one that
@@ -88,7 +70,7 @@
      */
     public static void setBuilderFactory(
             DocumentBuilderFactory documentBuilderFactory) {
-        BUILDER_FACTORY = documentBuilderFactory;
+        BUILDER_FACTORY.setFactory(documentBuilderFactory);
     }
 
     /**
@@ -119,11 +101,6 @@
     public static Document parseDocument(InputStream stream)
             throws ParserConfigurationException, SAXException, IOException {
         DocumentBuilder docBuilder = BUILDER_FACTORY.newDocumentBuilder();
-
-        // Set an error handler to prevent parsers from printing error messages
-        // to standard output!
-        docBuilder.setErrorHandler(new DefaultHandler());
-
         return docBuilder.parse(stream);
     }
 
Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java
===================================================================
--- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java	(revision 0)
+++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java	(working copy)
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the \"License\"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an \"AS IS\" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class ParserTest extends TestCase {
+
+    // see <http://en.wikipedia.org/wiki/Billion_laughs#Details>
+    public void testBillionLaughs() throws UnsupportedEncodingException {
+
+        String testBody = "<?xml version=\"1.0\"?>" + "<!DOCTYPE lolz [" + " <!ENTITY lol \"lol\">" + " <!ELEMENT lolz (#PCDATA)>"
+                + " <!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">"
+                + " <!ENTITY lol2 \"&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;\">"
+                + " <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">"
+                + " <!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">"
+                + " <!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">"
+                + " <!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">"
+                + " <!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">"
+                + " <!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">"
+                + " <!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">" + "]>" + "<lolz>&lol9;</lolz>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            DomUtil.parseDocument(is);
+            fail("parsing this document should cause an exception");
+        } catch (Exception expected) {
+        }
+    }
+
+    public void testExternalEntities() throws IOException {
+
+        String dname = "target";
+        String fname = "test.xml";
+
+        File f = new File(dname, fname);
+        OutputStream os = new FileOutputStream(f);
+        os.write("testdata".getBytes());
+        os.close();
+
+        String testBody = "<?xml version='1.0'?>\n<!DOCTYPE foo [" + " <!ENTITY test SYSTEM \"file:" + dname + "/" + fname + "\">"
+                + "]>\n<foo>&test;</foo>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            Document d = DomUtil.parseDocument(is);
+            Element root = d.getDocumentElement();
+            String text = DomUtil.getText(root);
+            fail("parsing this document should cause an exception, but the following external content was included: " + text);
+        } catch (Exception expected) {
+        }
+    }
+
+    public void testCustomEntityResolver() throws ParserConfigurationException, SAXException, IOException {
+
+        try {
+            DocumentBuilderFactory dbf = new DocumentBuilderFactory() {
+
+                DocumentBuilderFactory def = DocumentBuilderFactory.newInstance();
+
+                @Override
+                public void setFeature(String name, boolean value) throws ParserConfigurationException {
+                    def.setFeature(name, value);
+                }
+
+                @Override
+                public void setAttribute(String name, Object value) throws IllegalArgumentException {
+                    def.setAttribute(name, value);
+                }
+
+                @Override
+                public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+                    DocumentBuilder db = def.newDocumentBuilder();
+                    db.setEntityResolver(new EntityResolver() {
+                        @Override
+                        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+                            if ("foo:test".equals(systemId)) {
+                                return new InputSource(new ByteArrayInputStream("foo&amp;bar".getBytes("UTF-8")));
+                            } else {
+                                return null;
+                            }
+                        }
+                    });
+                    return db;
+                }
+
+                @Override
+                public boolean getFeature(String name) throws ParserConfigurationException {
+                    return def.getFeature(name);
+                }
+
+                @Override
+                public Object getAttribute(String name) throws IllegalArgumentException {
+                    return def.getAttribute(name);
+                }
+            };
+
+            DomUtil.setBuilderFactory(dbf);
+            String testBody = "<?xml version='1.0'?>\n<!DOCTYPE foo [" + " <!ENTITY test SYSTEM \"foo:test\">"
+                    + "]>\n<foo>&test;</foo>";
+            InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+            Document d = DomUtil.parseDocument(is);
+            Element root = d.getDocumentElement();
+            String text = DomUtil.getText(root);
+            assertEquals("custom entity resolver apparently not called", "foo&bar", text);
+        } finally {
+            DomUtil.setBuilderFactory(null);
+        }
+    }
+}
\ No newline at end of file
Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java
===================================================================
--- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java	(revision 1678308)
+++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java	(working copy)
@@ -33,6 +33,7 @@
         TestSuite suite = new TestSuite("org.apache.jackrabbit.webdav.xml tests");
 
         suite.addTestSuite(NamespaceTest.class);
+        suite.addTestSuite(ParserTest.class);
 
         return suite;
     }
CVE-2015-1833: Jackrabbit WebDAV XXE vulnerability

Severity: Important

Vendor: The Apache Software Foundation

Versions Affected:
Jackrabbit 2.0.0 - 2.0.5
Jackrabbit 2.2.0 - 2.2.13
Jackrabbit 2.4.0 - 2.4.5
Jackrabbit 2.6.0 - 2.6.5
Jackrabbit 2.8.0
Jackrabbit 2.10.0
The unsupported Jackrabbit 1.x versions may be also affected

Impact:
The contents of files and other network resources that are accessible to the server running the WebDAV component may be exposed to users.

Description:
When processing a WebDAV request body containing XML, the XML parser can be instructed to read content from network resources accessible to the host, identified by URI schemes such as "http(s)" or  "file". Depending on the WebDAV request, this can not only be used to trigger internal network requests, but might also be used to insert said content into the request, potentially exposing it to the attacker and others (for instance, by inserting said content in a WebDAV property value using a PROPPATCH request). See also IETF RFC 4918, Section 20.6.

Mitigation:
This vulnerability only affects systems that use Jackrabbit's WebDAV component. If this isn't in use, no immediate action is required.

Solution:
2.10.x users should upgrade to 2.10.1
2.8.x users should obtain latest source from svn or apply the patch which will be included in 2.8.1
2.6.x users should obtain latest source from svn or apply the patch which will be included in 2.6.6
2.4.x users should obtain latest source from svn or apply the patch which will be included in 2.4.6
2.2.x users should apply the patch which will be included in 2.2.14
2.0.x users should apply the patch which will be included in 2.0.6
Patches are attached to <https://issues.apache.org/jira/browse/JCR-3883>


Example:

The following payload, sent in a WebDAV PROPPATCH request, will read the file "/etc/passwd" and include it's textual content in the JCR property "test" of the identified resource. The value could then be retrieved using a WebDAV PROPFIND request, JCR API calls, etc.

<?xml version="1.0"?> 
<!DOCTYPE propertyupdate [ 
  <!ENTITY xee SYSTEM "file:///etc/passwd"> 
]> 
<propertyupdate xmlns="DAV:">
  <set>
    <prop>
      <test xmlns="">&xee;</test>
    </prop>
  </set>
</propertyupdate>

Credit:
This issue was discovered by Mikhail Egorov <0ang3el@xxxxxxxxx>
Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java
===================================================================
--- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java	(revision 1678320)
+++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java	(working copy)
@@ -27,6 +27,7 @@
 import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.MultiStatus;
 import org.apache.jackrabbit.webdav.header.Header;
+import org.apache.jackrabbit.webdav.xml.DavDocumentBuilderFactory;
 import org.apache.jackrabbit.webdav.xml.XmlSerializable;
 import org.apache.jackrabbit.webdav.xml.DomUtil;
 import org.slf4j.Logger;
@@ -39,6 +40,7 @@
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -49,7 +51,7 @@
 
     private static Logger log = LoggerFactory.getLogger(DavMethodBase.class);
 
-    static final DocumentBuilderFactory BUILDER_FACTORY = DomUtil.BUILDER_FACTORY;
+    static final DavDocumentBuilderFactory BUILDER_FACTORY = DomUtil.BUILDER_FACTORY;
 
     private boolean success;
     private Document responseDocument;
Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
===================================================================
--- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java	(revision 1678320)
+++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java	(working copy)
@@ -28,7 +28,6 @@
 import org.w3c.dom.Text;
 import org.w3c.dom.NamedNodeMap;
 
-import javax.xml.parsers.DocumentBuilderFactory;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -40,16 +39,10 @@
     private static Logger log = LoggerFactory.getLogger(DomUtil.class);
 
     /**
-     * Constant for <code>DocumentBuilderFactory</code> which is used
+     * Constant for <code>DavDocumentBuilderFactory</code> which is used
      * widely to create new <code>Document</code>s
      */
-    public static DocumentBuilderFactory BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
-    static {
-        BUILDER_FACTORY.setNamespaceAware(true);
-        BUILDER_FACTORY.setIgnoringComments(true);
-        BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
-        BUILDER_FACTORY.setCoalescing(true);
-    }
+    public static DavDocumentBuilderFactory BUILDER_FACTORY = new DavDocumentBuilderFactory();
 
     /**
      * Returns the value of the named attribute of the current element.
Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java
===================================================================
--- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java	(revision 0)
+++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java	(working copy)
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the \"License\"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an \"AS IS\" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class ParserTest extends TestCase {
+
+    // see <http://en.wikipedia.org/wiki/Billion_laughs#Details>
+    public void testBillionLaughs() throws UnsupportedEncodingException {
+
+        String testBody = "<?xml version=\"1.0\"?>" + "<!DOCTYPE lolz [" + " <!ENTITY lol \"lol\">" + " <!ELEMENT lolz (#PCDATA)>"
+                + " <!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">"
+                + " <!ENTITY lol2 \"&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;\">"
+                + " <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">"
+                + " <!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">"
+                + " <!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">"
+                + " <!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">"
+                + " <!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">"
+                + " <!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">"
+                + " <!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">" + "]>" + "<lolz>&lol9;</lolz>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            DomUtil.BUILDER_FACTORY.newDocumentBuilder().parse(is);
+            fail("parsing this document should cause an exception");
+        } catch (Exception expected) {
+        }
+    }
+
+    public void testExternalEntities() throws IOException {
+
+        String dname = "target";
+        String fname = "test.xml";
+
+        File f = new File(dname, fname);
+        OutputStream os = new FileOutputStream(f);
+        os.write("testdata".getBytes());
+        os.close();
+
+        String testBody = "<?xml version='1.0'?>\n<!DOCTYPE foo [" + " <!ENTITY test SYSTEM \"file:" + dname + "/" + fname + "\">"
+                + "]>\n<foo>&test;</foo>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            Document d = DomUtil.BUILDER_FACTORY.newDocumentBuilder().parse(is);
+            Element root = d.getDocumentElement();
+            String text = DomUtil.getText(root);
+            fail("parsing this document should cause an exception, but the following external content was included: " + text);
+        } catch (Exception expected) {
+        }
+    }
+}
\ No newline at end of file
Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java
===================================================================
--- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java	(revision 0)
+++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java	(working copy)
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that includes all testcases for package org.apache.jackrabbit.webdav.xml.
+ */
+public class TestAll extends TestCase {
+
+    /**
+     * Returns a <code>Test</code> suite that executes all tests inside this
+     * package.
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("org.apache.jackrabbit.webdav.xml tests");
+
+        suite.addTestSuite(ParserTest.class);
+
+        return suite;
+    }
+}
Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java
===================================================================
--- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java	(revision 0)
+++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java	(working copy)
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import java.io.IOException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Custom {@link DocumentBuilderFactory} extended for use in WebDAV.
+ */
+public class DavDocumentBuilderFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DomUtil.class);
+
+    private final DocumentBuilderFactory DEFAULT_FACTORY = createFactory();
+
+    private DocumentBuilderFactory BUILDER_FACTORY = DEFAULT_FACTORY;
+
+    private DocumentBuilderFactory createFactory() {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setIgnoringComments(true);
+        factory.setIgnoringElementContentWhitespace(true);
+        factory.setCoalescing(true);
+        try {
+            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        } catch (ParserConfigurationException e) {
+            LOG.warn("Secure XML processing is not supported", e);
+        } catch (AbstractMethodError e) {
+            LOG.warn("Secure XML processing is not supported", e);
+        }
+        return factory;
+    }
+
+    public void setFactory(DocumentBuilderFactory documentBuilderFactory) {
+        LOG.debug("DocumentBuilderFactory changed to: " + documentBuilderFactory);
+        BUILDER_FACTORY = documentBuilderFactory != null ? documentBuilderFactory : DEFAULT_FACTORY;
+    }
+
+    /**
+     * An entity resolver that does not allow external entity resolution. See
+     * RFC 4918, Section 20.6
+     */
+    private static final EntityResolver DEFAULT_ENTITY_RESOLVER = new EntityResolver() {
+        public InputSource resolveEntity(String publicId, String systemId) throws IOException {
+            LOG.debug("Resolution of external entities in XML payload not supported - publicId: " + publicId + ", systemId: "
+                    + systemId);
+            throw new IOException("This parser does not support resolution of external entities (publicId: " + publicId
+                    + ", systemId: " + systemId + ")");
+        }
+    };
+
+    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilder db = BUILDER_FACTORY.newDocumentBuilder();
+        if (BUILDER_FACTORY == DEFAULT_FACTORY) {
+            // if this is the default factory: set the default entity resolver as well
+            db.setEntityResolver(DEFAULT_ENTITY_RESOLVER);
+        }
+        db.setErrorHandler(new DefaultHandler());
+        return db;
+    }
+}
Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
===================================================================
--- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java	(revision 1678321)
+++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java	(working copy)
@@ -30,7 +30,6 @@
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
-import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
@@ -48,27 +47,11 @@
     private static Logger log = LoggerFactory.getLogger(DomUtil.class);
 
     /**
-     * Constant for <code>DocumentBuilderFactory</code> which is used
+     * Constant for <code>DavDocumentBuilderFactory</code> which is used
      * to create and parse DOM documents.
      */
-    private static DocumentBuilderFactory BUILDER_FACTORY = createFactory();
+    private static DavDocumentBuilderFactory BUILDER_FACTORY = new DavDocumentBuilderFactory();
 
-    private static DocumentBuilderFactory createFactory() {
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        factory.setNamespaceAware(true);
-        factory.setIgnoringComments(true);
-        factory.setIgnoringElementContentWhitespace(true);
-        factory.setCoalescing(true);
-        try {
-            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-        } catch (ParserConfigurationException e) {
-            log.warn("Secure XML processing is not supported", e);
-        } catch (AbstractMethodError e) {
-            log.warn("Secure XML processing is not supported", e);
-        }
-        return factory;
-    }
-
     /**
      * Support the replacement of {@link #BUILDER_FACTORY}. This is useful
      * for injecting a customized BuilderFactory, for example with one that
@@ -80,7 +63,7 @@
      */
     public static void setBuilderFactory(
             DocumentBuilderFactory documentBuilderFactory) {
-        BUILDER_FACTORY = documentBuilderFactory;
+        BUILDER_FACTORY.setFactory(documentBuilderFactory);
     }
 
     /**
Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java
===================================================================
--- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java	(revision 0)
+++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java	(working copy)
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the \"License\"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an \"AS IS\" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class ParserTest extends TestCase {
+
+    // see <http://en.wikipedia.org/wiki/Billion_laughs#Details>
+    public void testBillionLaughs() throws UnsupportedEncodingException {
+
+        String testBody = "<?xml version=\"1.0\"?>" + "<!DOCTYPE lolz [" + " <!ENTITY lol \"lol\">" + " <!ELEMENT lolz (#PCDATA)>"
+                + " <!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">"
+                + " <!ENTITY lol2 \"&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;\">"
+                + " <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">"
+                + " <!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">"
+                + " <!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">"
+                + " <!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">"
+                + " <!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">"
+                + " <!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">"
+                + " <!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">" + "]>" + "<lolz>&lol9;</lolz>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            DomUtil.parseDocument(is);
+            fail("parsing this document should cause an exception");
+        } catch (Exception expected) {
+        }
+    }
+
+    public void testExternalEntities() throws IOException {
+
+        String dname = "target";
+        String fname = "test.xml";
+
+        File f = new File(dname, fname);
+        OutputStream os = new FileOutputStream(f);
+        os.write("testdata".getBytes());
+        os.close();
+
+        String testBody = "<?xml version='1.0'?>\n<!DOCTYPE foo [" + " <!ENTITY test SYSTEM \"file:" + dname + "/" + fname + "\">"
+                + "]>\n<foo>&test;</foo>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            Document d = DomUtil.parseDocument(is);
+            Element root = d.getDocumentElement();
+            String text = DomUtil.getText(root);
+            fail("parsing this document should cause an exception, but the following external content was included: " + text);
+        } catch (Exception expected) {
+        }
+    }
+}
\ No newline at end of file
Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java
===================================================================
--- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java	(revision 1678321)
+++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java	(working copy)
@@ -33,6 +33,7 @@
         TestSuite suite = new TestSuite("org.apache.jackrabbit.webdav.xml tests");
 
         suite.addTestSuite(NamespaceTest.class);
+        suite.addTestSuite(ParserTest.class);
 
         return suite;
     }