Blob Blame History Raw
--- conf/web.xml.orig	2014-06-16 14:44:40.800412000 -0400
+++ conf/web.xml	2014-06-16 15:16:19.075817000 -0400
@@ -88,10 +88,10 @@
   <!--                       globalXsltFile[null]                           -->
   <!--                                                                      -->
   <!--   globalXsltFile      Site wide configuration version of             -->
-  <!--                       localXsltFile This argument is expected        -->
-  <!--                       to be a physical file. [null]                  -->
-  <!--                                                                      -->
-  <!--                                                                      -->
+  <!--                       localXsltFile This argument must be a          -->
+  <!--                       relative path that points to a location below  -->
+  <!--                       either $CATALINA_BASE/conf (checked first)     -->
+  <!--                       or $CATALINA_HOME/conf (checked second).       -->
 
     <servlet>
         <servlet-name>default</servlet-name>
--- java/org/apache/catalina/servlets/DefaultServlet.java.orig	2014-06-16 14:44:40.820409000 -0400
+++ java/org/apache/catalina/servlets/DefaultServlet.java	2014-06-16 17:31:56.392767000 -0400
@@ -36,6 +36,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.Locale;
 import java.util.StringTokenizer;
 
 import javax.naming.InitialContext;
@@ -53,10 +54,14 @@
 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;
 
@@ -72,7 +77,10 @@
 import org.apache.naming.resources.ResourceAttributes;
 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;
 /**
  * <p>The default resource-serving servlet for most web applications,
  * used to serve static resources such as HTML pages and images.
@@ -122,6 +130,10 @@
 
     private static final long serialVersionUID = 1L;
 
+    private static final DocumentBuilderFactory factory;
+
+    private static final SecureEntityResolver secureEntityResolver =
+            new SecureEntityResolver();
     // ----------------------------------------------------- Instance Variables
 
 
@@ -227,6 +239,9 @@
         urlEncoder.addSafeCharacter('.');
         urlEncoder.addSafeCharacter('*');
         urlEncoder.addSafeCharacter('/');
+        factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setValidating(false);
     }
 
 
@@ -1243,13 +1258,11 @@
     protected InputStream render(String contextPath, CacheEntry cacheEntry)
         throws IOException, ServletException {
 
-        InputStream xsltInputStream =
-            findXsltInputStream(cacheEntry.context);
-
-        if (xsltInputStream==null) {
+        Source xsltSource = findXsltInputStream(cacheEntry.context);
+        if (xsltSource ==null) {
             return renderHtml(contextPath, cacheEntry);
         }
-        return renderXml(contextPath, cacheEntry, xsltInputStream);
+        return renderXml(contextPath, cacheEntry, xsltSource);
 
     }
 
@@ -1262,7 +1275,7 @@
      */
     protected InputStream renderXml(String contextPath,
                                     CacheEntry cacheEntry,
-                                    InputStream xsltInputStream)
+                                    Source xsltSource)
         throws IOException, ServletException {
 
         StringBuilder sb = new StringBuilder();
@@ -1356,8 +1369,7 @@
         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");
@@ -1578,7 +1590,7 @@
     /**
      * Return the xsl template inputstream (if possible)
      */
-    protected InputStream findXsltInputStream(DirContext directory)
+    protected Source findXsltInputStream(DirContext directory)
         throws IOException {
 
         if (localXsltFile != null) {
@@ -1586,8 +1598,13 @@
                 Object obj = directory.lookup(localXsltFile);
                 if ((obj != null) && (obj instanceof Resource)) {
                     InputStream is = ((Resource) obj).streamContent();
-                    if (is != null)
-                        return is;
+                    if (is != null) {
+                        if (Globals.IS_SECURITY_ENABLED) {
+                            return secureXslt(is);
+                        } else {
+                            return new StreamSource(is);
+                        }
+                    }
                 }
             } catch (NamingException e) {
                 if (debug > 10)
@@ -1598,8 +1615,13 @@
         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");
@@ -1608,25 +1630,111 @@
         /*  Open and read in file in one fell swoop to reduce chance
          *  chance of leaving handle open.
          */
-        if (globalXsltFile!=null) {
-            FileInputStream fis = null;
+       if (globalXsltFile != null) {
+          File f = validateGlobalXsltFile();
+          if (f != null ){
+              FileInputStream fis = null;
+              try {
+                 fis = new FileInputStream(f);
+                 byte b[] = new byte[(int)f.length()]; /* danger! */
+                 fis.read(b);
+                 return new StreamSource(new ByteArrayInputStream(b));
+              } finally {
+                  if (fis != null) {
+                      try {
+                          fis.close();
+                      } catch(IOException ioe) {
+                          // ignore
+                      }
+                  }
+              }
+           }
+        }
 
-            try {
-                File f = new File(globalXsltFile);
-                if (f.exists()){
-                    fis =new FileInputStream(f);
-                    byte b[] = new byte[(int)f.length()]; /* danger! */
-                    fis.read(b);
-                    return new ByteArrayInputStream(b);
-                }
-            } finally {
-                if (fis!=null)
-                    fis.close();
+        return null;
+
+    }
+
+    private File validateGlobalXsltFile() {
+        
+        File result = null;
+        String base = System.getProperty(Globals.CATALINA_BASE_PROP);
+        
+        if (base != null) {
+            File baseConf = new File(base, "conf");
+            result = validateGlobalXsltFile(baseConf);
+        }
+        
+        if (result == null) {
+            String home = System.getProperty(Globals.CATALINA_HOME_PROP);
+            if (home != null && !home.equals(base)) {
+                File homeConf = new File(home, "conf");
+                result = validateGlobalXsltFile(homeConf);
             }
         }
 
-        return null;
+        return result;
+    }
+
 
+    private File validateGlobalXsltFile(File base) {
+        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 {
+            if (!candidate.getCanonicalPath().startsWith(base.getCanonicalPath())) {
+                return null;
+            }
+        } catch (IOException ioe) {
+            return null;
+        }
+
+        // Next check that an .xsl or .xslt file has been specified
+        String nameLower = candidate.getName().toLowerCase(Locale.ENGLISH);
+        if (!nameLower.endsWith(".xslt") && !nameLower.endsWith(".xsl")) {
+            return null;
+        }
+
+        return candidate;
+    }
+
+    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 e) {
+            if (debug > 0) {
+                log(e.getMessage(), e);
+            }
+        } catch (SAXException e) {
+            if (debug > 0) {
+                log(e.getMessage(), e);
+            }
+        } catch (IOException e) {
+            if (debug > 0) {
+                log(e.getMessage(), e);
+            }
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return result;
     }
 
 
@@ -2150,4 +2258,34 @@
             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));
+        }
+    }
 }
+
--- java/org/apache/catalina/servlets/LocalStrings.properties.orig	2014-06-16 14:44:40.830411000 -0400
+++ java/org/apache/catalina/servlets/LocalStrings.properties	2014-06-16 16:15:08.577726000 -0400
@@ -13,6 +13,10 @@
 # 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.directorylistingfor=Directory Listing for:
 defaultservlet.upto=Up to:
--- webapps/docs/default-servlet.xml.orig	2014-06-16 14:44:40.836413000 -0400
+++ webapps/docs/default-servlet.xml	2014-06-16 16:17:41.419241000 -0400
@@ -110,22 +110,24 @@
     <th valign='top'>globalXsltFile</th>
     <td valign='top'>
         If you wish to customize your directory listing, you
-        can use an XSL transformation. This value is an absolute
-        file name which be used for all directory listings.
-        This can be overridden per context and/or per directory. See
-        <strong>contextXsltFile</strong> and <strong>localXsltFile</strong>
-        below. The format of the xml is shown below.
+        can use an XSL transformation. This value is a relative file name (to
+         either $CATALINA_BASE/conf/ or $CATALINA_HOME/conf/) which will be used
+         for all directory listings. This can be overridden per context and/or
+         per directory. See <strong>contextXsltFile</strong> and
+         <strong>localXsltFile</strong> below. The format of the xml is shown
+         below.
     </td>
   </tr>
   <tr>
     <th valign='top'>contextXsltFile</th>
     <td valign='top'>
         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
         <code>globalXsltFile</code> does not exist, then the default
+        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
         directory listing will be shown.
     </td>
   </tr>
@@ -133,11 +135,12 @@
     <th valign='top'>localXsltFile</th>
     <td valign='top'>
         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