diff --git a/java/org/apache/catalina/Context.java b/java/org/apache/catalina/Context.java index 03e087bc784c..21517a07cb47 100644 --- a/java/org/apache/catalina/Context.java +++ b/java/org/apache/catalina/Context.java @@ -674,6 +674,25 @@ public void setSessionCookiePathUsesTrailingSlash( public void setTldNamespaceAware(boolean tldNamespaceAware); + /** + * Will the parsing of web.xml, web-fragment.xml, *.tld, *.jspx, *.tagx and + * tagplugin.xml files for this Context block the use of external entities? + * + * @return true if access to external entities is blocked + */ + public boolean getXmlBlockExternal(); + + + /** + * Controls whether the parsing of web.xml, web-fragment.xml, *.tld, *.jspx, + * *.tagx and tagplugin.xml files for this Context will block the use of + * external entities. + * + * @param xmlBlockExternal true to block external entities + */ + public void setXmlBlockExternal(boolean xmlBlockExternal); + + /** * Will the parsing of *.tld files for this Context be performed by a * validating parser? diff --git a/java/org/apache/catalina/Globals.java b/java/org/apache/catalina/Globals.java index 56694e76a2e1..78210d306f8b 100644 --- a/java/org/apache/catalina/Globals.java +++ b/java/org/apache/catalina/Globals.java @@ -316,4 +316,15 @@ public final class Globals { */ public static final String JASPER_XML_VALIDATION_TLD_INIT_PARAM = "org.apache.jasper.XML_VALIDATE_TLD"; + + + /** + * Name of the ServletContext init-param that determines if the JSP engine + * will block external entities from being used in *.tld, *.jspx, *.tagx and + * tagplugin.xml files. + *

+ * This must be kept in sync with org.apache.jasper.Constants + */ + public static final String JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM = + "org.apache.jasper.XML_BLOCK_EXTERNAL"; } diff --git a/java/org/apache/catalina/ant/ValidatorTask.java b/java/org/apache/catalina/ant/ValidatorTask.java index 4c2272f60f13..68f229974f62 100644 --- a/java/org/apache/catalina/ant/ValidatorTask.java +++ b/java/org/apache/catalina/ant/ValidatorTask.java @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.InputStream; +import org.apache.catalina.Globals; import org.apache.catalina.startup.Constants; import org.apache.tomcat.util.descriptor.DigesterFactory; import org.apache.tomcat.util.digester.Digester; @@ -87,7 +88,10 @@ public void execute() throws BuildException { Thread.currentThread().setContextClassLoader (ValidatorTask.class.getClassLoader()); - Digester digester = DigesterFactory.newDigester(true, true, null); + // Called through trusted manager interface. If running under a + // SecurityManager assume that untrusted applications may be deployed. + Digester digester = DigesterFactory.newDigester( + true, true, null, Globals.IS_SECURITY_ENABLED); try { file = file.getCanonicalFile(); InputStream stream = diff --git a/java/org/apache/catalina/core/ApplicationContext.java b/java/org/apache/catalina/core/ApplicationContext.java index 504b5c2365be..96dd3ec78ce0 100644 --- a/java/org/apache/catalina/core/ApplicationContext.java +++ b/java/org/apache/catalina/core/ApplicationContext.java @@ -320,12 +320,20 @@ public String getContextPath() { */ @Override public String getInitParameter(final String name) { - // Special handling for XML validation as the context setting must + // Special handling for XML settings as the context setting must // always override anything that might have been set by an application. if (Globals.JASPER_XML_VALIDATION_TLD_INIT_PARAM.equals(name) && context.getTldValidation()) { return "true"; } + if (Globals.JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM.equals(name)) { + if (context.getXmlBlockExternal()) { + return "true"; + } else if (Globals.IS_SECURITY_ENABLED) { + // System admin has explicitly changed the default + return "false"; + } + } return parameters.get(name); } @@ -338,11 +346,14 @@ public String getInitParameter(final String name) { public Enumeration getInitParameterNames() { Set names = new HashSet(); names.addAll(parameters.keySet()); - // Special handling for XML validation as this attribute will always be - // available if validation has been enabled on the context + // Special handling for XML settings as these attributes will always be + // available if they have been set on the context if (context.getTldValidation()) { names.add(Globals.JASPER_XML_VALIDATION_TLD_INIT_PARAM); } + if (context.getXmlBlockExternal() || Globals.IS_SECURITY_ENABLED) { + names.add(Globals.JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM); + } return Collections.enumeration(names); } diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java index 531424081742..f7d0a364caef 100644 --- a/java/org/apache/catalina/core/StandardContext.java +++ b/java/org/apache/catalina/core/StandardContext.java @@ -698,6 +698,13 @@ public StandardContext() { protected int cacheMaxSize = 10240; // 10 MB + + /** + * Attribute used to turn on/off the use of external entities. + */ + private boolean xmlBlockExternal = Globals.IS_SECURITY_ENABLED; + + /** * Cache object max size in KB. */ @@ -6624,6 +6631,18 @@ public void setTldNamespaceAware(boolean tldNamespaceAware){ } + @Override + public void setXmlBlockExternal(boolean xmlBlockExternal) { + this.xmlBlockExternal = xmlBlockExternal; + } + + + @Override + public boolean getXmlBlockExternal() { + return xmlBlockExternal; + } + + @Override public void setTldValidation(boolean tldValidation){ this.tldValidation = tldValidation; diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java index 80080e086b6f..4c8213c46361 100644 --- a/java/org/apache/catalina/startup/ContextConfig.java +++ b/java/org/apache/catalina/startup/ContextConfig.java @@ -516,14 +516,16 @@ protected void authenticatorConfig() { public void createWebXmlDigester(boolean namespaceAware, boolean validation) { + boolean blockExternal = context.getXmlBlockExternal(); + webRuleSet = new WebRuleSet(false); webDigester = DigesterFactory.newDigester(validation, - namespaceAware, webRuleSet); + namespaceAware, webRuleSet, blockExternal); webDigester.getParser(); webFragmentRuleSet = new WebRuleSet(true); webFragmentDigester = DigesterFactory.newDigester(validation, - namespaceAware, webFragmentRuleSet); + namespaceAware, webFragmentRuleSet, blockExternal); webFragmentDigester.getParser(); } diff --git a/java/org/apache/catalina/startup/FailedContext.java b/java/org/apache/catalina/startup/FailedContext.java index 3ece2a9951b0..f8459efbdc6d 100644 --- a/java/org/apache/catalina/startup/FailedContext.java +++ b/java/org/apache/catalina/startup/FailedContext.java @@ -427,6 +427,11 @@ public void setXmlValidation(boolean xmlValidation) { /* NO-OP */ } @Override public void setTldValidation(boolean tldValidation) { /* NO-OP */ } + @Override + public boolean getXmlBlockExternal() { return true; } + @Override + public void setXmlBlockExternal(boolean xmlBlockExternal) { /* NO-OP */ } + @Override public boolean getTldValidation() { return false; } diff --git a/java/org/apache/catalina/startup/TldConfig.java b/java/org/apache/catalina/startup/TldConfig.java index 8127c0b56389..cd114fbaa86d 100644 --- a/java/org/apache/catalina/startup/TldConfig.java +++ b/java/org/apache/catalina/startup/TldConfig.java @@ -85,20 +85,21 @@ public final class TldConfig implements LifecycleListener { * Create (if necessary) and return a Digester configured to process the * tld. */ - private static Digester createTldDigester(boolean validation) { + private static Digester createTldDigester(boolean validation, + boolean blockExternal) { Digester digester = null; if (!validation) { if (tldDigesters[0] == null) { tldDigesters[0] = DigesterFactory.newDigester(validation, - true, new TldRuleSet()); + true, new TldRuleSet(), blockExternal); tldDigesters[0].getParser(); } digester = tldDigesters[0]; } else { if (tldDigesters[1] == null) { tldDigesters[1] = DigesterFactory.newDigester(validation, - true, new TldRuleSet()); + true, new TldRuleSet(), blockExternal); tldDigesters[1].getParser(); } digester = tldDigesters[1]; @@ -567,7 +568,8 @@ public void lifecycleEvent(LifecycleEvent event) { private void init() { if (tldDigester == null){ - tldDigester = createTldDigester(context.getTldValidation()); + tldDigester = createTldDigester(context.getTldValidation(), + context.getXmlBlockExternal()); } } diff --git a/java/org/apache/jasper/Constants.java b/java/org/apache/jasper/Constants.java index 75838dc2a9ed..142c66242b37 100644 --- a/java/org/apache/jasper/Constants.java +++ b/java/org/apache/jasper/Constants.java @@ -241,4 +241,13 @@ public class Constants { */ public static final String XML_VALIDATION_TLD_INIT_PARAM = "org.apache.jasper.XML_VALIDATE_TLD"; + + /** + * Name of the ServletContext init-param that determines if the XML parsers + * will block the resolution of external entities. + *

+ * This must be kept in sync with org.apache.catalina.Globals + */ + public static final String XML_BLOCK_EXTERNAL_INIT_PARAM = + "org.apache.jasper.XML_BLOCK_EXTERNAL"; } diff --git a/java/org/apache/jasper/JspC.java b/java/org/apache/jasper/JspC.java index eac77846a309..9c88b18bc7eb 100644 --- a/java/org/apache/jasper/JspC.java +++ b/java/org/apache/jasper/JspC.java @@ -127,6 +127,7 @@ public class JspC extends Task implements Options { protected static final String SWITCH_SMAP = "-smap"; protected static final String SWITCH_DUMP_SMAP = "-dumpsmap"; protected static final String SWITCH_VALIDATE_TLD = "-validateTld"; + protected static final String SWITCH_BLOCK_EXTERNAL = "-blockExternal"; protected static final String SHOW_SUCCESS ="-s"; protected static final String LIST_ERRORS = "-l"; protected static final int INC_WEBXML = 10; @@ -158,6 +159,7 @@ public class JspC extends Task implements Options { protected boolean trimSpaces = false; protected boolean genStringAsCharArray = false; protected boolean validateTld; + protected boolean blockExternal; protected boolean xpoweredBy; protected boolean mappedFile = false; protected boolean poolingEnabled = true; @@ -367,6 +369,8 @@ public void setArgs(String[] arg) throws JasperException { smapDumped = true; } else if (tok.equals(SWITCH_VALIDATE_TLD)) { setValidateTld(true); + } else if (tok.equals(SWITCH_BLOCK_EXTERNAL)) { + setBlockExternal(true); } else { if (tok.startsWith("-")) { throw new JasperException("Unrecognized option: " + tok + @@ -854,6 +858,14 @@ public boolean isValidateTld() { return validateTld; } + public void setBlockExternal( boolean b ) { + this.blockExternal = b; + } + + public boolean isBlockExternal() { + return blockExternal; + } + public void setListErrors( boolean b ) { listErrors = b; } @@ -1435,6 +1447,9 @@ protected void initServletContext() { if (isValidateTld()) { context.setInitParameter(Constants.XML_VALIDATION_TLD_INIT_PARAM, "true"); } + if (isBlockExternal()) { + context.setInitParameter(Constants.XML_BLOCK_EXTERNAL_INIT_PARAM, "true"); + } rctxt = new JspRuntimeContext(context, this); jspConfig = new JspConfig(context); diff --git a/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java b/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java index 91af39e90bda..562f365c1e04 100644 --- a/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java +++ b/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java @@ -23,6 +23,7 @@ import java.util.Set; import java.util.Vector; +import javax.servlet.ServletContext; import javax.servlet.jsp.tagext.FunctionInfo; import javax.servlet.jsp.tagext.TagFileInfo; import javax.servlet.jsp.tagext.TagInfo; @@ -124,11 +125,21 @@ public ImplicitTagLibraryInfo(JspCompilationContext ctxt, pi.addDependant(path, ctxt.getLastModified(path)); } + ServletContext servletContext = ctxt.getServletContext(); boolean validate = Boolean.parseBoolean( - ctxt.getServletContext().getInitParameter( + servletContext.getInitParameter( Constants.XML_VALIDATION_TLD_INIT_PARAM)); - - ParserUtils pu = new ParserUtils(validate); + String blockExternalString = + servletContext.getInitParameter( + Constants.XML_BLOCK_EXTERNAL_INIT_PARAM); + boolean blockExternal; + if (blockExternalString == null) { + blockExternal = Constants.IS_SECURITY_ENABLED; + } else { + blockExternal = Boolean.parseBoolean(blockExternalString); + } + + ParserUtils pu = new ParserUtils(validate, blockExternal); TreeNode tld = pu.parseXMLDocument(uri, in); if (tld.findAttribute("version") != null) { diff --git a/java/org/apache/jasper/compiler/JspConfig.java b/java/org/apache/jasper/compiler/JspConfig.java index 570a6d5e342f..f74bdce1c668 100644 --- a/java/org/apache/jasper/compiler/JspConfig.java +++ b/java/org/apache/jasper/compiler/JspConfig.java @@ -80,10 +80,18 @@ private void processWebDotXml() throws JasperException { boolean validate = Boolean.parseBoolean( ctxt.getInitParameter(Constants.XML_VALIDATION_TLD_INIT_PARAM)); + String blockExternalString = + ctxt.getInitParameter(Constants.XML_BLOCK_EXTERNAL_INIT_PARAM); + boolean blockExternal; + if (blockExternalString == null) { + blockExternal = Constants.IS_SECURITY_ENABLED; + } else { + blockExternal = Boolean.parseBoolean(blockExternalString); + } TreeNode webApp = null; if (webXml.getInputSource() != null) { - ParserUtils pu = new ParserUtils(validate); + ParserUtils pu = new ParserUtils(validate, blockExternal); webApp = pu.parseXMLDocument(webXml.getSystemId(), webXml.getInputSource()); } diff --git a/java/org/apache/jasper/compiler/JspDocumentParser.java b/java/org/apache/jasper/compiler/JspDocumentParser.java index 08c2477b8292..d42f1405742c 100644 --- a/java/org/apache/jasper/compiler/JspDocumentParser.java +++ b/java/org/apache/jasper/compiler/JspDocumentParser.java @@ -30,8 +30,11 @@ import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; +import org.apache.jasper.Constants; import org.apache.jasper.JasperException; import org.apache.jasper.JspCompilationContext; +import org.apache.tomcat.util.descriptor.DigesterFactory; +import org.apache.tomcat.util.descriptor.LocalResolver; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.Locator; @@ -39,6 +42,7 @@ import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.EntityResolver2; import org.xml.sax.helpers.AttributesImpl; /** @@ -91,6 +95,7 @@ class JspDocumentParser private boolean inDTD; private boolean isValidating; + private final EntityResolver2 entityResolver; private ErrorDispatcher err; private boolean isTagFile; @@ -119,6 +124,20 @@ public JspDocumentParser( this.isTagFile = isTagFile; this.directivesOnly = directivesOnly; this.isTop = true; + + String blockExternalString = ctxt.getServletContext().getInitParameter( + Constants.XML_BLOCK_EXTERNAL_INIT_PARAM); + boolean blockExternal; + if (blockExternalString == null) { + blockExternal = Constants.IS_SECURITY_ENABLED; + } else { + blockExternal = Boolean.parseBoolean(blockExternalString); + } + + this.entityResolver = new LocalResolver( + DigesterFactory.SERVLET_API_PUBLIC_IDS, + DigesterFactory.SERVLET_API_SYSTEM_IDS, + blockExternal); } /* @@ -239,12 +258,29 @@ private void addInclude(Node parent, List files) throws SAXException { } } + + @Override + public InputSource getExternalSubset(String name, String baseURI) + throws SAXException, IOException { + return entityResolver.getExternalSubset(name, baseURI); + } + + + + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + return entityResolver.resolveEntity(publicId, systemId); + } + + @Override public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException { - return null; + return entityResolver.resolveEntity(name, publicId, baseURI, systemId); } + /* * Receives notification of the start of an element. * diff --git a/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java b/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java index 36fe86ff8d6d..e83015894bd7 100644 --- a/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java +++ b/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Vector; +import javax.servlet.ServletContext; import javax.servlet.jsp.tagext.FunctionInfo; import javax.servlet.jsp.tagext.PageData; import javax.servlet.jsp.tagext.TagAttributeInfo; @@ -213,12 +214,20 @@ private void parseTLD(String uri, InputStream in, JarResource jarResource) Vector tagFileVector = new Vector(); Hashtable functionTable = new Hashtable(); - boolean validate = Boolean.parseBoolean( - ctxt.getServletContext().getInitParameter( - Constants.XML_VALIDATION_TLD_INIT_PARAM)); + ServletContext servletContext = ctxt.getServletContext(); + boolean validate = Boolean.parseBoolean(servletContext.getInitParameter( + Constants.XML_VALIDATION_TLD_INIT_PARAM)); + String blockExternalString = servletContext.getInitParameter( + Constants.XML_BLOCK_EXTERNAL_INIT_PARAM); + boolean blockExternal; + if (blockExternalString == null) { + blockExternal = Constants.IS_SECURITY_ENABLED; + } else { + blockExternal = Boolean.parseBoolean(blockExternalString); + } // Create an iterator over the child elements of our element - ParserUtils pu = new ParserUtils(validate); + ParserUtils pu = new ParserUtils(validate, blockExternal); TreeNode tld = pu.parseXMLDocument(uri, in); // Check to see if the root element contains a 'version' diff --git a/java/org/apache/jasper/compiler/TagPluginManager.java b/java/org/apache/jasper/compiler/TagPluginManager.java index c61419486726..7fb6d805cc34 100644 --- a/java/org/apache/jasper/compiler/TagPluginManager.java +++ b/java/org/apache/jasper/compiler/TagPluginManager.java @@ -25,6 +25,7 @@ import javax.servlet.ServletContext; +import org.apache.jasper.Constants; import org.apache.jasper.JasperException; import org.apache.jasper.compiler.tagplugin.TagPlugin; import org.apache.jasper.compiler.tagplugin.TagPluginContext; @@ -119,8 +120,18 @@ private void init(ErrorDispatcher err) throws JasperException { private void loadTagPlugins(ErrorDispatcher err, InputStream is) throws JasperException { - TreeNode root = - (new ParserUtils(false)).parseXMLDocument(TAG_PLUGINS_XML, is); + String blockExternalString = ctxt.getInitParameter( + Constants.XML_BLOCK_EXTERNAL_INIT_PARAM); + boolean blockExternal; + if (blockExternalString == null) { + blockExternal = Constants.IS_SECURITY_ENABLED; + } else { + blockExternal = Boolean.parseBoolean(blockExternalString); + } + + ParserUtils pu = new ParserUtils(false, blockExternal); + + TreeNode root = pu.parseXMLDocument(TAG_PLUGINS_XML, is); if (root == null) { return; } diff --git a/java/org/apache/jasper/compiler/TldLocationsCache.java b/java/org/apache/jasper/compiler/TldLocationsCache.java index f2ccbd1cdab3..f8a68c00700e 100644 --- a/java/org/apache/jasper/compiler/TldLocationsCache.java +++ b/java/org/apache/jasper/compiler/TldLocationsCache.java @@ -289,10 +289,20 @@ private void tldScanWebXml() throws Exception { boolean validate = Boolean.parseBoolean( ctxt.getInitParameter( Constants.XML_VALIDATION_TLD_INIT_PARAM)); - + String blockExternalString = ctxt.getInitParameter( + Constants.XML_BLOCK_EXTERNAL_INIT_PARAM); + boolean blockExternal; + if (blockExternalString == null) { + blockExternal = Constants.IS_SECURITY_ENABLED; + } else { + blockExternal = Boolean.parseBoolean(blockExternalString); + } + // Parse the web application deployment descriptor + ParserUtils pu = new ParserUtils(validate, blockExternal); + TreeNode webtld = null; - webtld = new ParserUtils(validate).parseXMLDocument(webXml.getSystemId(), + webtld = pu.parseXMLDocument(webXml.getSystemId(), webXml.getInputSource()); // Allow taglib to be an element of the root or jsp-config (JSP2.0) @@ -498,9 +508,17 @@ private void tldScanStream(String resourcePath, String entryName, boolean validate = Boolean.parseBoolean( ctxt.getInitParameter( Constants.XML_VALIDATION_TLD_INIT_PARAM)); - - TreeNode tld = new ParserUtils(validate).parseXMLDocument( - resourcePath, stream); + String blockExternalString = ctxt.getInitParameter( + Constants.XML_BLOCK_EXTERNAL_INIT_PARAM); + boolean blockExternal; + if (blockExternalString == null) { + blockExternal = Constants.IS_SECURITY_ENABLED; + } else { + blockExternal = Boolean.parseBoolean(blockExternalString); + } + + ParserUtils pu = new ParserUtils(validate, blockExternal); + TreeNode tld = pu.parseXMLDocument(resourcePath, stream); TreeNode uriNode = tld.findChild("uri"); if (uriNode != null) { String body = uriNode.getBody(); diff --git a/java/org/apache/jasper/xmlparser/ParserUtils.java b/java/org/apache/jasper/xmlparser/ParserUtils.java index 94a2502b3b5f..668cfa4d2c8f 100644 --- a/java/org/apache/jasper/xmlparser/ParserUtils.java +++ b/java/org/apache/jasper/xmlparser/ParserUtils.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.jasper.xmlparser; import java.io.IOException; @@ -24,9 +23,11 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.apache.jasper.Constants; import org.apache.jasper.JasperException; import org.apache.jasper.compiler.Localizer; import org.apache.tomcat.util.descriptor.DigesterFactory; +import org.apache.tomcat.util.descriptor.LocalResolver; import org.apache.tomcat.util.descriptor.XmlErrorHandler; import org.w3c.dom.Comment; import org.w3c.dom.Document; @@ -49,7 +50,6 @@ * @author Craig R. McClanahan * @version $Id$ */ - public class ParserUtils { /** @@ -60,12 +60,25 @@ public class ParserUtils { /** * An entity resolver for use when parsing XML documents. */ - static EntityResolver entityResolver = DigesterFactory.SERVLET_API_RESOLVER; + static EntityResolver entityResolver; + + private final EntityResolver entityResolverInstance; private final boolean validating; public ParserUtils(boolean validating) { + this(validating, Constants.IS_SECURITY_ENABLED); + } + + public ParserUtils(boolean validating, boolean blockExternal) { this.validating = validating; + if (entityResolver == null) { + this.entityResolverInstance = new LocalResolver( + DigesterFactory.SERVLET_API_PUBLIC_IDS, + DigesterFactory.SERVLET_API_SYSTEM_IDS, blockExternal); + } else { + this.entityResolverInstance = entityResolver; + } } // --------------------------------------------------------- Public Methods @@ -92,7 +105,7 @@ public TreeNode parseXMLDocument(String location, InputSource is) factory.setNamespaceAware(true); factory.setValidating(validating); DocumentBuilder builder = factory.newDocumentBuilder(); - builder.setEntityResolver(entityResolver); + builder.setEntityResolver(entityResolverInstance); builder.setErrorHandler(errorHandler); document = builder.parse(is); } catch (ParserConfigurationException ex) { diff --git a/java/org/apache/tomcat/util/descriptor/DigesterFactory.java b/java/org/apache/tomcat/util/descriptor/DigesterFactory.java index e094d8f1b061..bf28a09714b5 100644 --- a/java/org/apache/tomcat/util/descriptor/DigesterFactory.java +++ b/java/org/apache/tomcat/util/descriptor/DigesterFactory.java @@ -17,6 +17,7 @@ package org.apache.tomcat.util.descriptor; import java.net.URL; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,6 +25,7 @@ import org.apache.tomcat.util.digester.Digester; import org.apache.tomcat.util.digester.RuleSet; +import org.xml.sax.ext.EntityResolver2; /** * Wrapper class around the Digester that hide Digester's initialization @@ -32,10 +34,16 @@ public class DigesterFactory { /** - * A resolver for the resources packaged in servlet-api.jar + * Mapping of well-known public IDs used by the Servlet API to the matching + * local resource. */ - public static final LocalResolver SERVLET_API_RESOLVER; + public static final Map SERVLET_API_PUBLIC_IDS; + /** + * Mapping of well-known system IDs used by the Servlet API to the matching + * local resource. + */ + public static final Map SERVLET_API_SYSTEM_IDS; static { Map publicIds = new HashMap(); @@ -81,7 +89,8 @@ public class DigesterFactory { addSelf(systemIds, "javaee_web_services_1_3.xsd"); addSelf(systemIds, "javaee_web_services_client_1_3.xsd"); - SERVLET_API_RESOLVER = new LocalResolver(publicIds, systemIds); + SERVLET_API_PUBLIC_IDS = Collections.unmodifiableMap(publicIds); + SERVLET_API_SYSTEM_IDS = Collections.unmodifiableMap(systemIds); } private static void addSelf(Map ids, String id) { @@ -103,15 +112,19 @@ private static String idFor(String url) { * @param xmlValidation turn on/off xml validation * @param xmlNamespaceAware turn on/off namespace validation * @param rule an instance of RuleSet used for parsing the xml. + * @param blockExternal turn on/off the blocking of external resources */ public static Digester newDigester(boolean xmlValidation, boolean xmlNamespaceAware, - RuleSet rule) { + RuleSet rule, + boolean blockExternal) { Digester digester = new Digester(); digester.setNamespaceAware(xmlNamespaceAware); digester.setValidating(xmlValidation); digester.setUseContextClassLoader(true); - digester.setEntityResolver(SERVLET_API_RESOLVER); + EntityResolver2 resolver = new LocalResolver(SERVLET_API_PUBLIC_IDS, + SERVLET_API_SYSTEM_IDS, blockExternal); + digester.setEntityResolver(resolver); if (rule != null) { digester.addRuleSet(rule); } diff --git a/java/org/apache/tomcat/util/descriptor/LocalResolver.java b/java/org/apache/tomcat/util/descriptor/LocalResolver.java index 55edf3d834ac..fb191459690d 100644 --- a/java/org/apache/tomcat/util/descriptor/LocalResolver.java +++ b/java/org/apache/tomcat/util/descriptor/LocalResolver.java @@ -16,6 +16,7 @@ */ package org.apache.tomcat.util.descriptor; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; @@ -23,6 +24,7 @@ import java.net.URL; import java.util.Map; +import org.apache.tomcat.util.res.StringManager; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.ext.EntityResolver2; @@ -32,22 +34,30 @@ */ public class LocalResolver implements EntityResolver2 { + private static final StringManager sm = + StringManager.getManager(Constants.PACKAGE_NAME); + private final Map publicIds; private final Map systemIds; - + private final boolean blockExternal; /** * Constructor providing mappings of public and system identifiers to local * resources. Each map contains a mapping from a well-known identifier to a * URL for a local resource path. * - * @param publicIds mapping of public identifiers to local resources - * @param systemIds mapping of system identifiers to local resources + * @param publicIds mapping of well-known public identifiers to local + * resources + * @param systemIds mapping of well-known system identifiers to local + * resources + * @param blockExternal are external resources blocked that are not + * well-known */ public LocalResolver(Map publicIds, - Map systemIds) { + Map systemIds, boolean blockExternal) { this.publicIds = publicIds; this.systemIds = systemIds; + this.blockExternal = blockExternal; } @@ -60,63 +70,77 @@ public InputSource resolveEntity(String publicId, String systemId) @Override public InputSource resolveEntity(String name, String publicId, - String baseURI, String systemId) throws SAXException, IOException { - - String resolved = resolve(publicId, systemId, baseURI); - if (resolved == null) { - return null; - } + String base, String systemId) throws SAXException, IOException { - InputSource is = new InputSource(resolved); - is.setPublicId(publicId); - return is; - } - - - @Override - public InputSource getExternalSubset(String name, String baseURI) - throws SAXException, IOException { - return null; - } - - - private String resolve(String publicId, String systemId, String baseURI) { - // try resolving using the publicId + // First try resolving using the publicId String resolved = publicIds.get(publicId); if (resolved != null) { - return resolved; + InputSource is = new InputSource(resolved); + is.setPublicId(publicId); + return is; } - // try resolving using the systemId + // If there is no systemId, can't try anything else if (systemId == null) { - return null; + throw new FileNotFoundException(sm.getString("localResolver.unresolvedEntity", + name, publicId, systemId, base)); } - systemId = resolve(baseURI, systemId); + // Try resolving with the supplied systemId resolved = systemIds.get(systemId); if (resolved != null) { - return resolved; + InputSource is = new InputSource(resolved); + is.setPublicId(publicId); + return is; } - // fall back to the supplied systemId - return systemId; - } - - - private static String resolve(String baseURI, String systemId) { + // Resolve the supplied systemId against the base + URI systemUri; try { - if (baseURI == null) { - return systemId; + if (base == null) { + systemUri = new URI(systemId); + } else { + // Can't use URI.resolve() because "jar:..." URLs are not valid + // hierarchical URIs so resolve() does not work. new URL() + // delegates to the jar: stream handler and it manages to figure + // it out. + URI baseUri = new URI(base); + systemUri = new URL(baseUri.toURL(), systemId).toURI(); } - URI systemUri = new URI(systemId); - if (systemUri.isAbsolute()) { - return systemId; - } - return new URL(new URL(baseURI), systemId).toString(); + systemUri = systemUri.normalize(); } catch (URISyntaxException e) { - return systemId; - } catch (MalformedURLException e) { - return systemId; + // May be caused by a | being used instead of a : in an absolute + // file URI on Windows. + if (blockExternal) { + // Absolute paths aren't allowed so block it + throw new MalformedURLException(e.getMessage()); + } else { + // See if the URLHandler can resolve it + return new InputSource(systemId); + } + } + if (systemUri.isAbsolute()) { + // Try the resolved systemId + resolved = systemIds.get(systemUri.toString()); + if (resolved != null) { + InputSource is = new InputSource(resolved); + is.setPublicId(publicId); + return is; + } + if (!blockExternal) { + InputSource is = new InputSource(systemUri.toString()); + is.setPublicId(publicId); + return is; + } } + throw new FileNotFoundException(sm.getString("localResolver.unresolvedEntity", + name, publicId, systemId, base)); + } + + + @Override + public InputSource getExternalSubset(String name, String baseURI) + throws SAXException, IOException { + return null; } -} \ No newline at end of file +} diff --git a/java/org/apache/tomcat/util/descriptor/LocalStrings.properties b/java/org/apache/tomcat/util/descriptor/LocalStrings.properties index f7d68915ad01..2e42fc719368 100644 --- a/java/org/apache/tomcat/util/descriptor/LocalStrings.properties +++ b/java/org/apache/tomcat/util/descriptor/LocalStrings.properties @@ -13,5 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +localResolver.unresolvedEntity=Could not resolve XML resource [{0}] with public ID [{1}], system ID [{2}] and base URI [{3}] to a known, local entity. + xmlErrorHandler.error=Non-fatal error [{0}] reported processing [{1}]. xmlErrorHandler.warning=Warning [{0}] reported processing [{1}]. diff --git a/test/javax/servlet/resources/TestSchemaValidation.java b/test/javax/servlet/resources/TestSchemaValidation.java index 46ab8ae6f035..59ec8d546a9c 100644 --- a/test/javax/servlet/resources/TestSchemaValidation.java +++ b/test/javax/servlet/resources/TestSchemaValidation.java @@ -31,8 +31,8 @@ public class TestSchemaValidation { @Test public void testWebapp_2_2() throws Exception { - Digester digester = - DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + Digester digester = DigesterFactory.newDigester( + true, true, new WebRuleSet(false), true); digester.push(new WebXml()); WebXml desc = (WebXml) digester.parse( new File("test/webapp-2.2/WEB-INF/web.xml")); @@ -42,8 +42,8 @@ public void testWebapp_2_2() throws Exception { @Test public void testWebapp_2_3() throws Exception { - Digester digester = - DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + Digester digester = DigesterFactory.newDigester( + true, true, new WebRuleSet(false), true); digester.push(new WebXml()); WebXml desc = (WebXml) digester.parse( new File("test/webapp-2.3/WEB-INF/web.xml")); @@ -53,8 +53,8 @@ public void testWebapp_2_3() throws Exception { @Test public void testWebapp_2_4() throws Exception { - Digester digester = - DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + Digester digester = DigesterFactory.newDigester( + true, true, new WebRuleSet(false), true); digester.push(new WebXml()); WebXml desc = (WebXml) digester.parse( new File("test/webapp-2.4/WEB-INF/web.xml")); @@ -63,8 +63,8 @@ public void testWebapp_2_4() throws Exception { @Test public void testWebapp_2_5() throws Exception { - Digester digester = - DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + Digester digester = DigesterFactory.newDigester( + true, true, new WebRuleSet(false), true); digester.push(new WebXml()); WebXml desc = (WebXml) digester.parse( new File("test/webapp-2.5/WEB-INF/web.xml")); @@ -73,8 +73,8 @@ public void testWebapp_2_5() throws Exception { @Test public void testWebapp_3_0() throws Exception { - Digester digester = - DigesterFactory.newDigester(true, true, new WebRuleSet(false)); + Digester digester = DigesterFactory.newDigester( + true, true, new WebRuleSet(false), true); digester.push(new WebXml()); WebXml desc = (WebXml) digester.parse( new File("test/webapp-3.0/WEB-INF/web.xml")); diff --git a/test/org/apache/catalina/core/TesterContext.java b/test/org/apache/catalina/core/TesterContext.java index 0c3a802defb7..3c802be07d74 100644 --- a/test/org/apache/catalina/core/TesterContext.java +++ b/test/org/apache/catalina/core/TesterContext.java @@ -635,6 +635,16 @@ public boolean getTldValidation() { return false; } + @Override + public boolean getXmlBlockExternal() { + return false; + } + + @Override + public void setXmlBlockExternal(boolean xmlBlockExternal) { + // NO-OP + } + @Override public boolean getTldNamespaceAware() { return true; diff --git a/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java b/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java index b41606b4a872..cf8b6b388cc8 100644 --- a/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java +++ b/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java @@ -16,6 +16,7 @@ */ package org.apache.tomcat.util.descriptor; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -34,7 +35,7 @@ public class TestLocalResolver { private final Map publicIds = new HashMap(); private final Map systemIds = new HashMap(); - private LocalResolver resolver = new LocalResolver(publicIds, systemIds); + private LocalResolver resolver = new LocalResolver(publicIds, systemIds, true); private String WEB_22_LOCAL; private String WEB_30_LOCAL; private String WEBCOMMON_30_LOCAL; @@ -53,25 +54,25 @@ public String urlFor(String id) { return ServletContext.class.getResource(id).toExternalForm(); } - @Test - public void unknownNullIdIsNull() throws IOException, SAXException { + @Test(expected = FileNotFoundException.class) + public void unknownNullId() throws IOException, SAXException { Assert.assertNull(resolver.resolveEntity(null, null)); } - @Test - public void unknownPublicIdIsNull() throws IOException, SAXException { + @Test(expected = FileNotFoundException.class) + public void unknownPublicId() throws IOException, SAXException { Assert.assertNull(resolver.resolveEntity("unknown", null)); } - @Test - public void unknownSystemIdIsReturned() throws IOException, SAXException { + @Test(expected = FileNotFoundException.class) + public void unknownSystemId() throws IOException, SAXException { InputSource source = resolver.resolveEntity(null, "unknown"); Assert.assertEquals(null, source.getPublicId()); Assert.assertEquals("unknown", source.getSystemId()); } - @Test - public void unknownSystemIdIsResolvedAgainstBaseURI() + @Test(expected = FileNotFoundException.class) + public void unknownRelativeSystemId() throws IOException, SAXException { InputSource source = resolver.resolveEntity( null, null, "http://example.com/home.html", "unknown"); @@ -121,4 +122,4 @@ public void absoluteSystemIdOverridesBaseURI() Assert.assertEquals(null, source.getPublicId()); Assert.assertEquals(WEB_30_LOCAL, source.getSystemId()); } -} \ No newline at end of file +} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index ea776b3d4b97..125dc090e496 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -216,6 +216,12 @@ 55851: Further fixes to enable SPNEGO authentication to work with IBM JDKs. Based on a patch by Arunav Sanyal. (markt) + + Add an option to the Context to control the blocking of XML external + entities when parsing XML configuration files and enable this blocking + by default when a security manager is used. The block is implemented via + a custom resolver to enable the logging of any blocked entities. (markt) + diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml index 15849f459f88..f0fe76e0b047 100644 --- a/webapps/docs/config/context.xml +++ b/webapps/docs/config/context.xml @@ -524,6 +524,16 @@ Context. If not specified, a standard default value will be used.

+ +

If the value of this flag is true, the parsing of + web.xml, web-fragment.xml, *.tld, + *.jspx, *.tagx and tagPlugins.xml + files for this web application will not permit external entities to be + loaded. If a SecurityManager is configured then the default + value of this attribute will be true, else the default + value will be false.

+
+

If the value of this flag is true, the parsing of web.xml and web-fragment.xml files for this diff --git a/webapps/docs/security-howto.xml b/webapps/docs/security-howto.xml index 56dc11f7cab9..ef4d34a5aaf9 100644 --- a/webapps/docs/security-howto.xml +++ b/webapps/docs/security-howto.xml @@ -179,6 +179,9 @@