--- 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 @@ - - - - + + + + default --- 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; /** *

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 @@ globalXsltFile 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 - contextXsltFile and localXsltFile - 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 contextXsltFile and + localXsltFile below. The format of the xml is shown + below. contextXsltFile You may also customize your directory listing by context by - configuring contextXsltFile. This should be a context - relative path (e.g.: /path/to/context.xslt). This - overrides globalXsltFile. If this value is present but a - file does not exist, then globalXsltFile will be used. If globalXsltFile does not exist, then the default + configuring contextXsltFile. This must be a context + relative path (e.g.: /path/to/context.xslt) to a file with + a .xsl or .xslt extension. This overrides + globalXsltFile. If this value is present but a file does + not exist, then globalXsltFile will be used. If directory listing will be shown. @@ -133,11 +135,12 @@ localXsltFile You may also customize your directory listing by directory by - configuring localXsltFile. This should be a relative - file name in the directory where the listing will take place. - This overrides globalXsltFile and - contextXsltFile. If this value is present but a file - does not exist, then contextXsltFile will be used. If + configuring localXsltFile. This must be a file in the + directory where the listing will take place to with a + .xsl or .xslt extension. This overrides + globalXsltFile and contextXsltFile. If this + value is present but a file does not exist, then + contextXsltFile will be used. If contextXsltFile does not exist, then globalXsltFile will be used. If globalXsltFile does not exist, then the default