diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 63c844a..f63fbaa 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -43,27 +43,27 @@
javax.xml.parsers.DocumentBuilderFactoryjavax.xml.parsers.DocumentBuilderFactoryorg.apache.commons.xml.secure.SecureDocumentBuilderFactoryjavax.xml.parsers.SAXParserFactoryjavax.xml.parsers.SAXParserFactoryorg.apache.commons.xml.secure.SecureSAXParserFactoryjavax.xml.validation.SchemaFactoryjavax.xml.validation.SchemaFactoryorg.apache.commons.xml.secure.SecureSchemaFactoryjavax.xml.transform.TransformerFactoryjavax.xml.transform.TransformerFactoryorg.apache.commons.xml.secure.SecureTransformerFactoryjavax.xml.stream.XMLInputFactoryjavax.xml.stream.XMLInputFactoryorg.apache.commons.xml.secure.SecureXMLInputFactoryjavax.xml.xpath.XPathFactoryjavax.xml.xpath.XPathFactoryorg.apache.commons.xml.secure.SecureXPathFactory
Defaults are also uneven. The stock JDK SAX and DOM parsers already prevent external entity resolution through
- FEATURE_SECURE_PROCESSING,
+ FEATURE_SECURE_PROCESSING,
and JAXP 1.5 conformant implementations ship reasonable defaults for most attacks. Others, such as standalone Xerces, Woodstox, or Saxon’s TrAX, need
further configuration before they reach the same baseline. A library author has no control over which implementation is on the classpath at runtime, so
the effective security posture of their code depends on a deployment decision made elsewhere.
@@ -233,7 +233,7 @@
jdk.xml.overrideDefaultParser feature (and Java system property
- of the same name) to switch to parsers instantiated through ServiceLoader.
+ of the same name) to switch to parsers instantiated through ServiceLoader.
org.apache.commons.xml.secure
parser and pass the result as a
- DOMSource
+ DOMSource
or
- SAXSource
+ SAXSource
. A stylesheet also chooses where the transform writes (
xsl:result-document):
the securing governs reads only, so restrict output destinations yourself when running an untrusted stylesheet (see the Threat
@@ -293,7 +293,7 @@
The
- SAXTransformerFactory
+ SAXTransformerFactory
extension methods,
newTransformerHandler(...),
newTemplatesHandler()
@@ -302,7 +302,7 @@
SecureTransformerFactory.newInstance(),
produce handlers, filters and
- Templates
+ Templates
carrying the same securing as the standard entry points: runtime
document()
resolves to empty content, and a filter with no caller-set parent parses its input through a secured reader. The SAX events you feed into a handler, and
@@ -336,10 +336,148 @@ http://apache.org/xml/features/disallow-doctype-decl to reject every document
carrying a DOCTYPE, on implementations that support the feature.
EntityResolver, XMLResolver, LSResourceResolver or URIResolver
+ EntityResolver, XMLResolver, LSResourceResolver or URIResolver
is consulted before the securing floor, so an allow-list and a deny-all are both one resolver away.
+ A resolver here serves the opposite purpose it does on a stock JAXP factory.
+ There, returning null hands the reference back to the parser, which fetches it;
+ on a secured factory, returning null leaves the reference unresolved,
+ and the securing floor answers it with empty content.
+ Whatever your resolver leaves unresolved is never fetched.
+
+ A resolver is therefore the way to opt a resource back in, + and returning a non-null result is how you say “this one is allowed”. + Your resolver is consulted before the floor and is never replaced by it, + and what it returns is honored even where the JAXP 1.5 external-access properties would deny the fetch, + because those properties do not apply to a resolved result. +
+
+ DTDs, external entities, and xi:include targets on
+ DocumentBuilder
+ and
+ XMLReader,
+ via
+ EntityResolver.
+ An InputSource carrying only the system identifier is the shortest way to allow one: the parser opens it itself.
+
+
+import org.xml.sax.InputSource;
+import org.apache.commons.xml.secure.SecureDocumentBuilderFactory;
+
+DocumentBuilder builder = SecureDocumentBuilderFactory.newInstance().newDocumentBuilder();
+builder.setEntityResolver((publicId, systemId) -> ALLOWED.contains(systemId) ? new InputSource(systemId) : null);
+
+
+
+ Every fetch on the schema path on
+ SchemaFactory,
+ Schema,
+ Validator
+ and
+ ValidatorHandler,
+ via
+ LSResourceResolver.
+ This one resolver answers for the schema documents a schema pulls in
+ (xs:include, xs:import, and xsi:schemaLocation hints)
+ and for the DTD and the external entities of the instance document being validated.
+ The type argument tells them apart, as DOM Level 3 Load and Save prescribes:
+ XMLConstants.W3C_XML_SCHEMA_NS_URI for a schema document,
+ XMLConstants.XML_DTD_NS_URI for a DTD or an entity.
+ A schema references its neighbors relatively, so resolve the system identifier against the base URI before matching it.
+
+
+import org.w3c.dom.bootstrap.DOMImplementationRegistry;
+import org.w3c.dom.ls.DOMImplementationLS;
+import org.w3c.dom.ls.LSInput;
+import org.apache.commons.xml.secure.SecureSchemaFactory;
+
+DOMImplementationLS domImplementationLS = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
+
+SchemaFactory factory = SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) -> {
+ String resolved = baseURI == null ? systemId : URI.create(baseURI).resolve(systemId).toString();
+ if (!ALLOWED.contains(resolved)) {
+ return null;
+ }
+ LSInput input = domImplementationLS.createLSInput();
+ input.setSystemId(resolved);
+ return input;
+});
+
+
+
+ Every fetch on the transform path on
+ TransformerFactory,
+ Templates
+ and
+ Transformer,
+ via
+ URIResolver.
+ This one resolver answers for the stylesheet modules pulled in at compile time
+ (xsl:include and xsl:import)
+ and for everything the transform fetches as it runs:
+ document(),
+ and on an XSLT 3.0 implementation the unparsed-text() family and json-doc() as well.
+ A StreamSource you return is re-parsed with a secured reader,
+ so the references inside the resource you allowed face the same floor again.
+ A function that cannot accept an empty document in place of what it asked for, unparsed-text() among them,
+ reports an error when the resolver declines rather than returning empty content;
+ either way the resource is not fetched.
+
+
+import javax.xml.transform.stream.StreamSource;
+import org.apache.commons.xml.secure.SecureTransformerFactory;
+
+TransformerFactory factory = SecureTransformerFactory.newInstance();
+factory.setURIResolver((href, base) -> {
+ String resolved = base == null ? href : URI.create(base).resolve(href).toString();
+ return ALLOWED.contains(resolved) ? new StreamSource(resolved) : null;
+});
+
+
+
+ Entities on the streaming path on
+ XMLInputFactory,
+ via
+ XMLResolver.
+ This is the one resolver that has to open the resource itself:
+ it must return an
+ InputStream,
+ an
+ XMLStreamReader
+ or an
+ XMLEventReader,
+ and any other type is silently ignored
+ (the stock JDK then falls back to fetching the identifier the document declared, not the one you returned).
+
+
+import org.apache.commons.xml.secure.SecureXMLInputFactory;
+
+XMLInputFactory factory = SecureXMLInputFactory.newInstance();
+factory.setXMLResolver((publicID, systemID, baseURI, namespace) -> {
+ String resolved = baseURI == null ? systemID : URI.create(baseURI).resolve(systemID).toString();
+ return ALLOWED.contains(resolved) ? URI.create(resolved).toURL().openStream() : null;
+});
+
+
+
As a temporary debugging measure, set the system property
org.apache.commons.xml.secure.throwOnUnresolved