You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2014/03/17 22:44:49 UTC

svn commit: r1578611 - in /tomcat/trunk: conf/web.xml java/org/apache/catalina/servlets/DefaultServlet.java java/org/apache/catalina/servlets/LocalStrings.properties webapps/docs/default-servlet.xml

Author: markt
Date: Mon Mar 17 21:44:48 2014
New Revision: 1578611

URL: http://svn.apache.org/r1578611
Log:
Prevent user supplied XSLTs from defining external entities

Modified:
    tomcat/trunk/conf/web.xml
    tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java
    tomcat/trunk/java/org/apache/catalina/servlets/LocalStrings.properties
    tomcat/trunk/webapps/docs/default-servlet.xml

Modified: tomcat/trunk/conf/web.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml?rev=1578611&r1=1578610&r2=1578611&view=diff
==============================================================================
--- tomcat/trunk/conf/web.xml (original)
+++ tomcat/trunk/conf/web.xml Mon Mar 17 21:44:48 2014
@@ -88,10 +88,12 @@
   <!--                       globalXsltFile[null]                           -->
   <!--                                                                      -->
   <!--   globalXsltFile      Site wide configuration version of             -->
-  <!--                       localXsltFile. This argument must be a         -->
-  <!--                       relative path that points to a location below  -->
-  <!--                       either $CATALINA_BASE/conf (checked first)     -->
-  <!--                       or $CATALINA_BASE/conf (checked second).[null] -->
+  <!--                       localXsltFile. This argument must either be an -->
+  <!--                       absolute or relative (to either                -->
+  <!--                       $CATALINA_BASE/conf or $CATALINA_HOME/conf)    -->
+  <!--                       path that points to a location below either    -->
+  <!--                       $CATALINA_BASE/conf (checked first) or         -->
+  <!--                       $CATALINA_HOME/conf (checked second).[null]    -->
 
     <servlet>
         <servlet-name>default</servlet-name>

Modified: tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java?rev=1578611&r1=1578610&r2=1578611&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java (original)
+++ tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java Mon Mar 17 21:44:48 2014
@@ -47,10 +47,14 @@ import javax.servlet.UnavailableExceptio
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.Source;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
 
@@ -64,6 +68,10 @@ import org.apache.catalina.util.RequestU
 import org.apache.catalina.util.ServerInfo;
 import org.apache.catalina.util.URLEncoder;
 import org.apache.tomcat.util.res.StringManager;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.EntityResolver2;
 
 
 /**
@@ -122,6 +130,11 @@ public class DefaultServlet extends Http
      */
     protected static final URLEncoder urlEncoder;
 
+    private static final DocumentBuilderFactory factory;
+
+    private static final SecureEntityResolver secureEntityResolver =
+            new SecureEntityResolver();
+
     /**
      * Full range marker.
      */
@@ -152,6 +165,10 @@ public class DefaultServlet extends Http
         urlEncoder.addSafeCharacter('.');
         urlEncoder.addSafeCharacter('*');
         urlEncoder.addSafeCharacter('/');
+
+        factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setValidating(false);
     }
 
 
@@ -1192,13 +1209,12 @@ public class DefaultServlet extends Http
     protected InputStream render(String contextPath, WebResource resource)
         throws IOException, ServletException {
 
-        InputStream xsltInputStream =
-            findXsltInputStream(resource);
+        Source xsltSource = findXsltSource(resource);
 
-        if (xsltInputStream==null) {
+        if (xsltSource == null) {
             return renderHtml(contextPath, resource);
         }
-        return renderXml(contextPath, resource, xsltInputStream);
+        return renderXml(contextPath, resource, xsltSource);
 
     }
 
@@ -1211,7 +1227,7 @@ public class DefaultServlet extends Http
      */
     protected InputStream renderXml(String contextPath,
                                     WebResource resource,
-                                    InputStream xsltInputStream)
+                                    Source xsltSource)
         throws IOException, ServletException {
 
         StringBuilder sb = new StringBuilder();
@@ -1292,8 +1308,7 @@ public class DefaultServlet extends Http
         try {
             TransformerFactory tFactory = TransformerFactory.newInstance();
             Source xmlSource = new StreamSource(new StringReader(sb.toString()));
-            Source xslSource = new StreamSource(xsltInputStream);
-            Transformer transformer = tFactory.newTransformer(xslSource);
+            Transformer transformer = tFactory.newTransformer(xsltSource);
 
             ByteArrayOutputStream stream = new ByteArrayOutputStream();
             OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
@@ -1496,9 +1511,9 @@ public class DefaultServlet extends Http
 
 
     /**
-     * Return the xsl template inputstream (if possible)
+     * Return a Source for the xsl template (if possible)
      */
-    protected InputStream findXsltInputStream(WebResource directory)
+    protected Source findXsltSource(WebResource directory)
         throws IOException {
 
         if (localXsltFile != null) {
@@ -1507,7 +1522,11 @@ public class DefaultServlet extends Http
             if (resource.isFile()) {
                 InputStream is = resource.getInputStream();
                 if (is != null) {
-                    return is;
+                    if (Globals.IS_SECURITY_ENABLED) {
+                        return secureXslt(is);
+                    } else {
+                        return new StreamSource(is);
+                    }
                 }
             }
             if (debug > 10) {
@@ -1518,8 +1537,13 @@ public class DefaultServlet extends Http
         if (contextXsltFile != null) {
             InputStream is =
                 getServletContext().getResourceAsStream(contextXsltFile);
-            if (is != null)
-                return is;
+            if (is != null) {
+                if (Globals.IS_SECURITY_ENABLED) {
+                    return secureXslt(is);
+                } else {
+                    return new StreamSource(is);
+                }
+            }
 
             if (debug > 10)
                 log("contextXsltFile '" + contextXsltFile + "' not found");
@@ -1530,11 +1554,11 @@ public class DefaultServlet extends Http
          */
         if (globalXsltFile != null) {
             File f = validateGlobalXsltFile();
-            if (f != null && f.exists()){
+            if (f != null){
                 try (FileInputStream fis = new FileInputStream(f)){
                     byte b[] = new byte[(int)f.length()]; /* danger! */
                     fis.read(b);
-                    return new ByteArrayInputStream(b);
+                    return new StreamSource(new ByteArrayInputStream(b));
                 }
             }
         }
@@ -1550,7 +1574,9 @@ public class DefaultServlet extends Http
         File result = validateGlobalXsltFile(baseConf);
         if (result == null) {
             File homeConf = new File(context.getCatalinaHome(), "conf");
-            result = validateGlobalXsltFile(homeConf);
+            if (!baseConf.equals(homeConf)) {
+                result = validateGlobalXsltFile(homeConf);
+            }
         }
 
         return result;
@@ -1558,7 +1584,14 @@ public class DefaultServlet extends Http
 
 
     private File validateGlobalXsltFile(File base) {
-        File candidate = new File(base, globalXsltFile);
+        File candidate = new File(globalXsltFile);
+        if (!candidate.isAbsolute()) {
+            candidate = new File(base, globalXsltFile);
+        }
+
+        if (!candidate.isFile()) {
+            return null;
+        }
 
         // First check that the resulting path is under the provided base
         try {
@@ -1569,9 +1602,9 @@ public class DefaultServlet extends Http
             return null;
         }
 
-        // Next check that an .xlt or .xslt file has been specified
+        // Next check that an .xsl or .xslt file has been specified
         String nameLower = candidate.getName().toLowerCase(Locale.ENGLISH);
-        if (!nameLower.endsWith(".xslt") && !nameLower.endsWith(".xlt")) {
+        if (!nameLower.endsWith(".xslt") && !nameLower.endsWith(".xsl")) {
             return null;
         }
 
@@ -1579,8 +1612,32 @@ public class DefaultServlet extends Http
     }
 
 
-    // -------------------------------------------------------- protected Methods
+    private Source secureXslt(InputStream is) {
+        // Need to filter out any external entities
+        Source result = null;
+        try {
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            builder.setEntityResolver(secureEntityResolver);
+            Document document = builder.parse(is);
+            result = new DOMSource(document);
+        } catch (ParserConfigurationException | SAXException | IOException e) {
+            if (debug > 0) {
+                log(e.getMessage(), e);
+            }
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return result;
+    }
+
 
+    // -------------------------------------------------------- protected Methods
 
     /**
      * Check if sendfile can be used.
@@ -2074,9 +2131,6 @@ public class DefaultServlet extends Http
     }
 
 
-    // ------------------------------------------------------ Range Inner Class
-
-
     protected static class Range {
 
         public long start;
@@ -2092,4 +2146,34 @@ public class DefaultServlet extends Http
             return (start >= 0) && (end >= 0) && (start <= end) && (length > 0);
         }
     }
+
+
+    /**
+     * This is secure in the sense that any attempt to use an external entity
+     * will trigger an exception.
+     */
+    private static class SecureEntityResolver implements EntityResolver2  {
+
+        @Override
+        public InputSource resolveEntity(String publicId, String systemId)
+                throws SAXException, IOException {
+            throw new SAXException(sm.getString("defaultServlet.blockExternalEntity",
+                    publicId, systemId));
+        }
+
+        @Override
+        public InputSource getExternalSubset(String name, String baseURI)
+                throws SAXException, IOException {
+            throw new SAXException(sm.getString("defaultServlet.blockExternalSubset",
+                    name, baseURI));
+        }
+
+        @Override
+        public InputSource resolveEntity(String name, String publicId,
+                String baseURI, String systemId) throws SAXException,
+                IOException {
+            throw new SAXException(sm.getString("defaultServlet.blockExternalEntity2",
+                    name, publicId, baseURI, systemId));
+        }
+    }
 }

Modified: tomcat/trunk/java/org/apache/catalina/servlets/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/servlets/LocalStrings.properties?rev=1578611&r1=1578610&r2=1578611&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/servlets/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/catalina/servlets/LocalStrings.properties Mon Mar 17 21:44:48 2014
@@ -13,6 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+defaultServlet.blockExternalEntity=Blocked access to external entity with publicId [{0}] and systemId [{0}]
+defaultServlet.blockExternalEntity2=Blocked access to external entity with name [{0}], publicId [{1}], baseURI [{2}] and systemId [{3}]
+defaultServlet.blockExternalSubset=Blocked access to external subset with name [{0}] and baseURI [{1}]
 defaultServlet.missingResource=The requested resource ({0}) is not available
 defaultservlet.skipfail=Only skipped [{0}] bytes when [{1}] were requested
 webdavservlet.jaxpfailed=JAXP initialization failed

Modified: tomcat/trunk/webapps/docs/default-servlet.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/default-servlet.xml?rev=1578611&r1=1578610&r2=1578611&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/default-servlet.xml (original)
+++ tomcat/trunk/webapps/docs/default-servlet.xml Mon Mar 17 21:44:48 2014
@@ -120,20 +120,22 @@ directory listings are disabled and debu
   </property>
   <property name="contextXsltFile">
         You may also customize your directory listing by context by
-        configuring <code>contextXsltFile</code>. This should be a context
-        relative path (e.g.: <code>/path/to/context.xslt</code>). This
-        overrides <code>globalXsltFile</code>. If this value is present but a
-        file does not exist, then <code>globalXsltFile</code> will be used. If
+        configuring <code>contextXsltFile</code>. This must be a context
+        relative path (e.g.: <code>/path/to/context.xslt</code>) to a file with
+        a <code>.xsl</code> or <code>.xslt</code> extension. This overrides
+        <code>globalXsltFile</code>. If this value is present but a file does
+        not exist, then <code>globalXsltFile</code> will be used. If
         <code>globalXsltFile</code> does not exist, then the default
         directory listing will be shown.
   </property>
   <property name="localXsltFile">
         You may also customize your directory listing by directory by
-        configuring <code>localXsltFile</code>. This should be a relative
-        file name in the directory where the listing will take place.
-        This overrides <code>globalXsltFile</code> and
-        <code>contextXsltFile</code>. If this value is present but a file
-        does not exist, then <code>contextXsltFile</code> will be used. If
+        configuring <code>localXsltFile</code>. This must be a file in the
+        directory where the listing will take place to with a
+        <code>.xsl</code> or <code>.xslt</code> extension. This overrides
+        <code>globalXsltFile</code> and <code>contextXsltFile</code>. If this
+        value is present but a file does not exist, then
+        <code>contextXsltFile</code> will be used. If
         <code>contextXsltFile</code> does not exist, then
         <code>globalXsltFile</code> will be used. If
         <code>globalXsltFile</code> does not exist, then the default



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org