--- 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