You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@commons.apache.org by "ppkarwasz (via GitHub)" <gi...@apache.org> on 2026/08/19 11:13:00 UTC
[PR] Harden the XPath InputSource evaluation surface [commons-xml]
ppkarwasz opened a new pull request, #44:
URL: https://github.com/apache/commons-xml/pull/44
Hardens the document parse behind the `InputSource`-taking XPath evaluation entry points.
## Problem
`FEATURE_SECURE_PROCESSING` on an `XPathFactory` governs only the XPath engine. The stock JDK and Apache Xalan implement `XPath.evaluate(String, InputSource)` and `XPathExpression.evaluate(InputSource)` by provisioning an internal document parser the feature does not reach, so external references inside the evaluated document were resolved at that parser's defaults. The library's own DocumentBuilder/SAXParser refuse the identical document.
## Changes
`XPathHardener`'s generic branch now returns a `HardeningXPathFactory` instead of the bare factory (the Saxon branch is unchanged: `SaxonProvider`'s `Configuration.makeParser` already hardens Saxon's document builds):
- `HardeningXPathFactory` wraps `newXPath()` in a `HardeningXPath`, delegating everything else raw.
- `HardeningXPath` performs the document build behind `evaluate(String, InputSource[, QName])` itself, through a hardened, namespace-aware `DocumentBuilder`, and evaluates the delegate against the parsed `Document`, so the engine's own parser never runs. An external reference inside the document resolves to empty on the resolver floor, like every other hardened parse. `compile(String)` wraps the compiled expression in a `HardeningXPathExpression`, which applies the same rewrite to `evaluate(InputSource[, QName])`. The `evaluateExpression` default methods added by Java 9 route through the overridden overloads, so the Java 8 build stays covered on newer runtimes.
The `newXPathFactory()` javadoc documents the covered entry points, and the threat model's *Scope and intended use* enumeration gains XPath objects (the report's tmd-2 correction).
## Tests
New `XPathInputSourceTest`, tagged `xpath`, which populates the previously empty `xpath` surefire group; it runs under test-stockjdk, test-xalan and test-xalan-xerces (Saxon's separate path is covered by `SaxonXPathExternalCallsTest` under `xpath3`):
- blocks-or-does-not-leak assertions for `XPath.evaluate` and the compiled `XPathExpression.evaluate` over a document whose external entity would otherwise surface in the result;
- a positive control evaluating an entity-free document end to end through the hardened pre-parse;
- a leak control proving the unconfigured engine resolves the entity, which also verified the new tests discriminate: with the wrapper removed, both hardened tests fail on all three executions.
`ShadingFootprintTest` expectations updated: the XPath closure gains the three wrappers plus the DOM hardener set it now parses through, and the whole-library count moves from 29 to 32.
Full `mvn clean` and plain `mvn` (checkstyle, spotbugs, pmd, javadoc, and the whole surefire matrix) green, plus a post-build non-vacuous `spotbugs:check`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856402678
##########
src/test/java/org/apache/commons/xml/ShadingFootprintTest.java:
##########
@@ -69,23 +70,25 @@ class ShadingFootprintTest {
private static final Set<String> STAX_HARDENER = set("StaxHardener", "HardeningXMLInputFactory", "FallbackIgnoreXMLResolver", HARDENING_EXCEPTION);
/**
- * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#harden(Source)}, so each builds on the full SAX closure below.
+ * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#hardenSource(Source)}, so each builds on the full SAX closure below; XPath
+ * additionally parses InputSource-taking evaluate calls through the DOM hardener, so its closure carries that set too.
*/
private static final Set<String> TRANSFORMER_HARDENER = saxParsersHardenerPlus("TransformerHardener", "HardeningTransformerFactory",
"HardeningTransformer", "HardeningTransformerHandler", "HardeningTemplates", "HardeningTemplatesHandler", "HardeningXMLFilter",
"FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1", "SaxonProvider$HardenedConfiguration"
, "SaxonProvider$SaxonProviderConfigurer");
- private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1",
- "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer");
+ private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider",
+ "SaxonProvider$1", "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer", "HardeningXPathFactory", "HardeningXPath",
+ "HardeningXPathExpression", "DocumentBuilderHardener", "HardeningDocumentBuilder", "HardeningDocumentBuilderFactory");
private static final Set<String> SCHEMA_HARDENER = saxParsersHardenerPlus("SchemaHardener", "HardeningSchemaFactory", "HardeningValidator",
"HardeningValidatorHandler", "HardeningSchema", "FallbackIgnoreLSResourceResolver");
/**
* Only the public {@link XmlFactories} entry, which news up every hardener, still pulls the whole library; this is its class count.
*/
- private static final int WHOLE_LIBRARY_SIZE = 32;
+ private static final int WHOLE_LIBRARY_SIZE = 35;
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/0e2ea993890132398dd23ddadd633c4131b2c4b6
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856405896
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ final String result;
+ try {
+ result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ } catch (final XPathExpressionException blocked) {
+ return; // Acceptable: the parse rejected the reference rather than resolving it to empty.
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/b914ebb8537636f08c417eb84a0a55b12a40e49c
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ final String result;
+ try {
+ result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ } catch (final XPathExpressionException blocked) {
+ return; // Acceptable: the parse rejected the reference rather than resolving it to empty.
+ }
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ }
+
+ @Test
+ void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
+ final String result;
+ try {
+ result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
+ } catch (final XPathExpressionException blocked) {
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/b914ebb8537636f08c417eb84a0a55b12a40e49c
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856273887
##########
src/main/java/org/apache/commons/xml/HardeningXPathFactory.java:
##########
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathFactoryConfigurationException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+/**
+ * {@link XPathFactory} wrapper that returns a {@link HardeningXPath} from {@link #newXPath()}.
+ *
+ * <p>Required because {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the factory governs only the XPath engine: the stock JDK and Apache Xalan
+ * implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document parser the feature does not reach.
+ * The wrapper performs that document build itself through a hardened parser instead; see {@link HardeningXPath}.</p>
+ */
+final class HardeningXPathFactory extends XPathFactory {
+
+ private final XPathFactory delegate;
+
+ HardeningXPathFactory(final XPathFactory delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public XPath newXPath() {
+ final XPath xpath = delegate.newXPath();
+ return xpath == null ? null : new HardeningXPath(xpath);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+ @Override
+ public boolean getFeature(final String name) throws XPathFactoryConfigurationException {
+ return delegate.getFeature(name);
+ }
+
+ @Override
+ public boolean isObjectModelSupported(final String objectModel) {
+ return delegate.isObjectModelSupported(objectModel);
+ }
+
+ @Override
+ public void setFeature(final String name, final boolean value) throws XPathFactoryConfigurationException {
+ delegate.setFeature(name, value);
+ }
+
+ @Override
+ public void setXPathFunctionResolver(final XPathFunctionResolver resolver) {
+ delegate.setXPathFunctionResolver(resolver);
+ }
+
+ @Override
+ public void setXPathVariableResolver(final XPathVariableResolver resolver) {
+ delegate.setXPathVariableResolver(resolver);
+ }
+ // </editor-fold>
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/98125dc2ef0a2657554786aa72b4b36430fb11fc
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3861889942
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor, while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ // Deterministic on every engine: the entity is declared in the internal subset and the floor resolves only its
+ // external content — to empty replacement text — so the pre-parse completes and the reference expands to nothing.
+ final String result = assertDoesNotThrow(
Review Comment:
For this PR it is fixed in https://github.com/apache/commons-xml/pull/44/commits/1ae58fea7c67a231f4e5d81e896cea4e8c8b4d89
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3857464396
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
Review Comment:
I rechecked an apparently these throws are now stale: both Xalan and Saxon are happy with the kind of empty resource they receive.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3857464396
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
Review Comment:
I rechecked an apparently these throws are now stale: both Xalan and Saxon are happy with the kind of empty resource they receive.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory merged PR #44:
URL: https://github.com/apache/commons-xml/pull/44
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3852955416
##########
src/main/java/org/apache/commons/xml/HardeningXPath.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link XPath} wrapper that performs the document build behind every {@link InputSource}-taking {@code evaluate} call with a hardened, namespace-aware
+ * {@link javax.xml.parsers.DocumentBuilder} and evaluates the delegate against the parsed {@link Document}, so the engine's own parser never runs.
+ *
+ * <p>The JAXP contract for {@link XPath#evaluate(String, InputSource, QName)} is "build a document from the source, then evaluate against it", and both the
+ * stock JDK and Apache Xalan provision an internal parser for that build which {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the
+ * {@link javax.xml.xpath.XPathFactory} does not reach. Parsing here puts the build on the library's resolver floor: an external reference inside the document
+ * resolves to empty content, so it is neither fetched nor leaked, and the evaluation proceeds on whatever the parse produced. {@link #compile(String)} wraps
+ * the compiled expression in a {@link HardeningXPathExpression} on the same terms.</p>
+ *
+ * <p>The {@code evaluateExpression} default methods added to the interface by Java 9 route through the {@code evaluate} overloads overridden here, so they
+ * carry the same rewrite on newer runtimes even though this class targets Java 8.</p>
+ */
+final class HardeningXPath implements XPath {
+
+ /**
+ * Parses the source through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder}, mirroring the namespace awareness of the parser the
+ * engine would have provisioned.
+ *
+ * @param source The document to evaluate against.
+ * @return The parsed document.
+ * @throws NullPointerException if {@code source} is {@code null}, per the {@link XPath} contract.
+ * @throws XPathExpressionException if the source cannot be parsed.
+ */
+ static Document parse(final InputSource source) throws XPathExpressionException {
+ if (source == null) {
+ throw new NullPointerException("source cannot be null");
+ }
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder().parse(source);
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new XPathExpressionException(e);
+ }
+ }
+
+ private final XPath delegate;
+
+ HardeningXPath(final XPath delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String evaluate(final String expression, final InputSource source) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source));
+ }
+
+ @Override
+ public Object evaluate(final String expression, final InputSource source, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source), returnType);
+ }
+
+ @Override
+ public XPathExpression compile(final String expression) throws XPathExpressionException {
+ final XPathExpression compiled = delegate.compile(expression);
+ return compiled == null ? null : new HardeningXPathExpression(compiled);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+ @Override
+ public String evaluate(final String expression, final Object item) throws XPathExpressionException {
+ return delegate.evaluate(expression, item);
+ }
+
+ @Override
+ public Object evaluate(final String expression, final Object item, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(expression, item, returnType);
+ }
+
+ @Override
+ public NamespaceContext getNamespaceContext() {
+ return delegate.getNamespaceContext();
+ }
+
+ @Override
+ public XPathFunctionResolver getXPathFunctionResolver() {
+ return delegate.getXPathFunctionResolver();
+ }
+
+ @Override
+ public XPathVariableResolver getXPathVariableResolver() {
+ return delegate.getXPathVariableResolver();
+ }
+
+ @Override
+ public void reset() {
+ delegate.reset();
+ }
+
+ @Override
+ public void setNamespaceContext(final NamespaceContext nsContext) {
+ delegate.setNamespaceContext(nsContext);
+ }
+
+ @Override
+ public void setXPathFunctionResolver(final XPathFunctionResolver resolver) {
+ delegate.setXPathFunctionResolver(resolver);
+ }
+
+ @Override
+ public void setXPathVariableResolver(final XPathVariableResolver resolver) {
+ delegate.setXPathVariableResolver(resolver);
+ }
+ // </editor-fold>
Review Comment:
Remove this comment noise please.
##########
src/main/java/org/apache/commons/xml/HardeningXPath.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link XPath} wrapper that performs the document build behind every {@link InputSource}-taking {@code evaluate} call with a hardened, namespace-aware
+ * {@link javax.xml.parsers.DocumentBuilder} and evaluates the delegate against the parsed {@link Document}, so the engine's own parser never runs.
+ *
+ * <p>The JAXP contract for {@link XPath#evaluate(String, InputSource, QName)} is "build a document from the source, then evaluate against it", and both the
+ * stock JDK and Apache Xalan provision an internal parser for that build which {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the
+ * {@link javax.xml.xpath.XPathFactory} does not reach. Parsing here puts the build on the library's resolver floor: an external reference inside the document
+ * resolves to empty content, so it is neither fetched nor leaked, and the evaluation proceeds on whatever the parse produced. {@link #compile(String)} wraps
+ * the compiled expression in a {@link HardeningXPathExpression} on the same terms.</p>
+ *
+ * <p>The {@code evaluateExpression} default methods added to the interface by Java 9 route through the {@code evaluate} overloads overridden here, so they
+ * carry the same rewrite on newer runtimes even though this class targets Java 8.</p>
+ */
+final class HardeningXPath implements XPath {
+
+ /**
+ * Parses the source through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder}, mirroring the namespace awareness of the parser the
+ * engine would have provisioned.
+ *
+ * @param source The document to evaluate against.
+ * @return The parsed document.
+ * @throws NullPointerException if {@code source} is {@code null}, per the {@link XPath} contract.
+ * @throws XPathExpressionException if the source cannot be parsed.
+ */
+ static Document parse(final InputSource source) throws XPathExpressionException {
+ if (source == null) {
+ throw new NullPointerException("source cannot be null");
+ }
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder().parse(source);
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new XPathExpressionException(e);
+ }
+ }
+
+ private final XPath delegate;
+
+ HardeningXPath(final XPath delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String evaluate(final String expression, final InputSource source) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source));
+ }
+
+ @Override
+ public Object evaluate(final String expression, final InputSource source, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source), returnType);
+ }
+
+ @Override
+ public XPathExpression compile(final String expression) throws XPathExpressionException {
+ final XPathExpression compiled = delegate.compile(expression);
+ return compiled == null ? null : new HardeningXPathExpression(compiled);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
Review Comment:
Remove this comment noise please.
##########
src/test/java/org/apache/commons/xml/ShadingFootprintTest.java:
##########
@@ -69,23 +70,25 @@ class ShadingFootprintTest {
private static final Set<String> STAX_HARDENER = set("StaxHardener", "HardeningXMLInputFactory", "FallbackIgnoreXMLResolver", HARDENING_EXCEPTION);
/**
- * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#harden(Source)}, so each builds on the full SAX closure below.
+ * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#hardenSource(Source)}, so each builds on the full SAX closure below; XPath
+ * additionally parses InputSource-taking evaluate calls through the DOM hardener, so its closure carries that set too.
*/
private static final Set<String> TRANSFORMER_HARDENER = saxParsersHardenerPlus("TransformerHardener", "HardeningTransformerFactory",
"HardeningTransformer", "HardeningTransformerHandler", "HardeningTemplates", "HardeningTemplatesHandler", "HardeningXMLFilter",
"FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1", "SaxonProvider$HardenedConfiguration"
, "SaxonProvider$SaxonProviderConfigurer");
- private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1",
- "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer");
+ private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider",
+ "SaxonProvider$1", "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer", "HardeningXPathFactory", "HardeningXPath",
+ "HardeningXPathExpression", "DocumentBuilderHardener", "HardeningDocumentBuilder", "HardeningDocumentBuilderFactory");
private static final Set<String> SCHEMA_HARDENER = saxParsersHardenerPlus("SchemaHardener", "HardeningSchemaFactory", "HardeningValidator",
"HardeningValidatorHandler", "HardeningSchema", "FallbackIgnoreLSResourceResolver");
/**
* Only the public {@link XmlFactories} entry, which news up every hardener, still pulls the whole library; this is its class count.
Review Comment:
What does "news up" means? Use plain English IMO.
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ final String result;
+ try {
+ result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ } catch (final XPathExpressionException blocked) {
+ return; // Acceptable: the parse rejected the reference rather than resolving it to empty.
Review Comment:
Same comment as in PR #42
Add a better comment or use `assertThrows()`
##########
src/main/java/org/apache/commons/xml/HardeningXPathFactory.java:
##########
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathFactoryConfigurationException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+/**
+ * {@link XPathFactory} wrapper that returns a {@link HardeningXPath} from {@link #newXPath()}.
+ *
+ * <p>Required because {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the factory governs only the XPath engine: the stock JDK and Apache Xalan
+ * implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document parser the feature does not reach.
+ * The wrapper performs that document build itself through a hardened parser instead; see {@link HardeningXPath}.</p>
+ */
+final class HardeningXPathFactory extends XPathFactory {
+
+ private final XPathFactory delegate;
+
+ HardeningXPathFactory(final XPathFactory delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public XPath newXPath() {
+ final XPath xpath = delegate.newXPath();
+ return xpath == null ? null : new HardeningXPath(xpath);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+ @Override
+ public boolean getFeature(final String name) throws XPathFactoryConfigurationException {
+ return delegate.getFeature(name);
+ }
+
+ @Override
+ public boolean isObjectModelSupported(final String objectModel) {
+ return delegate.isObjectModelSupported(objectModel);
+ }
+
+ @Override
+ public void setFeature(final String name, final boolean value) throws XPathFactoryConfigurationException {
+ delegate.setFeature(name, value);
+ }
+
+ @Override
+ public void setXPathFunctionResolver(final XPathFunctionResolver resolver) {
+ delegate.setXPathFunctionResolver(resolver);
+ }
+
+ @Override
+ public void setXPathVariableResolver(final XPathVariableResolver resolver) {
+ delegate.setXPathVariableResolver(resolver);
+ }
+ // </editor-fold>
Review Comment:
Remove this // comment.
##########
src/main/java/org/apache/commons/xml/HardeningXPathFactory.java:
##########
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathFactoryConfigurationException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+/**
+ * {@link XPathFactory} wrapper that returns a {@link HardeningXPath} from {@link #newXPath()}.
+ *
+ * <p>Required because {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the factory governs only the XPath engine: the stock JDK and Apache Xalan
+ * implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document parser the feature does not reach.
+ * The wrapper performs that document build itself through a hardened parser instead; see {@link HardeningXPath}.</p>
+ */
+final class HardeningXPathFactory extends XPathFactory {
+
+ private final XPathFactory delegate;
+
+ HardeningXPathFactory(final XPathFactory delegate) {
+ this.delegate = delegate;
Review Comment:
Add Objects.requireNonNull().
##########
src/main/java/org/apache/commons/xml/HardeningXPathExpression.java:
##########
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+
+import org.xml.sax.InputSource;
+
+/**
+ * {@link XPathExpression} wrapper that applies the same {@link InputSource} rewrite as {@link HardeningXPath} to the compiled evaluation entry points.
+ *
+ * <p>{@link HardeningXPath#compile(String)} returns one of these, so {@link #evaluate(InputSource)} and {@link #evaluate(InputSource, QName)} build the
+ * document through a hardened, namespace-aware parser instead of the engine's own; the {@code evaluateExpression} default methods added by Java 9 route
+ * through these overloads as well.</p>
+ */
+final class HardeningXPathExpression implements XPathExpression {
+
+ private final XPathExpression delegate;
+
+ HardeningXPathExpression(final XPathExpression delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String evaluate(final InputSource source) throws XPathExpressionException {
+ return delegate.evaluate(HardeningXPath.parse(source));
+ }
+
+ @Override
+ public Object evaluate(final InputSource source, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(HardeningXPath.parse(source), returnType);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+ @Override
+ public String evaluate(final Object item) throws XPathExpressionException {
+ return delegate.evaluate(item);
+ }
+
+ @Override
+ public Object evaluate(final Object item, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(item, returnType);
+ }
+ // </editor-fold>
Review Comment:
Remove this junk // comment.
##########
src/test/java/org/apache/commons/xml/ShadingFootprintTest.java:
##########
@@ -69,23 +70,25 @@ class ShadingFootprintTest {
private static final Set<String> STAX_HARDENER = set("StaxHardener", "HardeningXMLInputFactory", "FallbackIgnoreXMLResolver", HARDENING_EXCEPTION);
/**
- * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#harden(Source)}, so each builds on the full SAX closure below.
+ * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#hardenSource(Source)}, so each builds on the full SAX closure below; XPath
+ * additionally parses InputSource-taking evaluate calls through the DOM hardener, so its closure carries that set too.
*/
private static final Set<String> TRANSFORMER_HARDENER = saxParsersHardenerPlus("TransformerHardener", "HardeningTransformerFactory",
"HardeningTransformer", "HardeningTransformerHandler", "HardeningTemplates", "HardeningTemplatesHandler", "HardeningXMLFilter",
"FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1", "SaxonProvider$HardenedConfiguration"
, "SaxonProvider$SaxonProviderConfigurer");
- private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1",
- "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer");
+ private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider",
+ "SaxonProvider$1", "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer", "HardeningXPathFactory", "HardeningXPath",
+ "HardeningXPathExpression", "DocumentBuilderHardener", "HardeningDocumentBuilder", "HardeningDocumentBuilderFactory");
private static final Set<String> SCHEMA_HARDENER = saxParsersHardenerPlus("SchemaHardener", "HardeningSchemaFactory", "HardeningValidator",
"HardeningValidatorHandler", "HardeningSchema", "FallbackIgnoreLSResourceResolver");
/**
* Only the public {@link XmlFactories} entry, which news up every hardener, still pulls the whole library; this is its class count.
*/
- private static final int WHOLE_LIBRARY_SIZE = 32;
+ private static final int WHOLE_LIBRARY_SIZE = 35;
Review Comment:
The Javadoc says "class count" but the name uses the ambiguous "SIZE", use "CLASS_COUNT" to remove the confusion.
##########
src/main/java/org/apache/commons/xml/HardeningXPath.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link XPath} wrapper that performs the document build behind every {@link InputSource}-taking {@code evaluate} call with a hardened, namespace-aware
+ * {@link javax.xml.parsers.DocumentBuilder} and evaluates the delegate against the parsed {@link Document}, so the engine's own parser never runs.
+ *
+ * <p>The JAXP contract for {@link XPath#evaluate(String, InputSource, QName)} is "build a document from the source, then evaluate against it", and both the
+ * stock JDK and Apache Xalan provision an internal parser for that build which {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the
+ * {@link javax.xml.xpath.XPathFactory} does not reach. Parsing here puts the build on the library's resolver floor: an external reference inside the document
+ * resolves to empty content, so it is neither fetched nor leaked, and the evaluation proceeds on whatever the parse produced. {@link #compile(String)} wraps
+ * the compiled expression in a {@link HardeningXPathExpression} on the same terms.</p>
+ *
+ * <p>The {@code evaluateExpression} default methods added to the interface by Java 9 route through the {@code evaluate} overloads overridden here, so they
+ * carry the same rewrite on newer runtimes even though this class targets Java 8.</p>
+ */
+final class HardeningXPath implements XPath {
+
+ /**
+ * Parses the source through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder}, mirroring the namespace awareness of the parser the
+ * engine would have provisioned.
+ *
+ * @param source The document to evaluate against.
+ * @return The parsed document.
+ * @throws NullPointerException if {@code source} is {@code null}, per the {@link XPath} contract.
+ * @throws XPathExpressionException if the source cannot be parsed.
+ */
+ static Document parse(final InputSource source) throws XPathExpressionException {
+ if (source == null) {
+ throw new NullPointerException("source cannot be null");
+ }
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder().parse(source);
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new XPathExpressionException(e);
+ }
+ }
+
+ private final XPath delegate;
+
+ HardeningXPath(final XPath delegate) {
+ this.delegate = delegate;
Review Comment:
Make invariant obvious:
```
this.delegate = Objects.requireNonNull(delegate, "delegate");
```
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ final String result;
+ try {
+ result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ } catch (final XPathExpressionException blocked) {
+ return; // Acceptable: the parse rejected the reference rather than resolving it to empty.
+ }
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ }
+
+ @Test
+ void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
+ final String result;
+ try {
+ result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
+ } catch (final XPathExpressionException blocked) {
Review Comment:
Same comment as in PR #42
Add a better comment or use `assertThrows()`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3857244402
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
Review Comment:
Same as before and in other PRs: if this is a non-deterministic failure, add a clearer comment explaining when it can happen; if it’s an invariant, use `assertThrows()` instead of using `try` with an empty `catch`.
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
+ }
+ }
+
+ @Test
+ void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the compiled XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
Review Comment:
Same as before and in other PRs: if this is a non-deterministic failure, add a clearer comment explaining when it can happen; if it’s an invariant, use `assertThrows()` instead of using `try` with an empty `catch`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on PR #44:
URL: https://github.com/apache/commons-xml/pull/44#issuecomment-5417007990
Applying the same analysis as in #42: I probed every combination that runs these tests (JDK 8–25 × the stock JDK and Xalan XPath engines, over both DOM parsers) and the hardened evaluation never throws — the entity is declared in the internal subset, so the floor only resolves its external *content* to an empty stream, a legal empty replacement text no parser can reject. Since not throwing is the invariant, c55c682 drops the tolerant try/catch and asserts the strict no-throw/no-leak outcome with `assertDoesNotThrow`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856166407
##########
src/main/java/org/apache/commons/xml/HardeningXPath.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link XPath} wrapper that performs the document build behind every {@link InputSource}-taking {@code evaluate} call with a hardened, namespace-aware
+ * {@link javax.xml.parsers.DocumentBuilder} and evaluates the delegate against the parsed {@link Document}, so the engine's own parser never runs.
+ *
+ * <p>The JAXP contract for {@link XPath#evaluate(String, InputSource, QName)} is "build a document from the source, then evaluate against it", and both the
+ * stock JDK and Apache Xalan provision an internal parser for that build which {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the
+ * {@link javax.xml.xpath.XPathFactory} does not reach. Parsing here puts the build on the library's resolver floor: an external reference inside the document
+ * resolves to empty content, so it is neither fetched nor leaked, and the evaluation proceeds on whatever the parse produced. {@link #compile(String)} wraps
+ * the compiled expression in a {@link HardeningXPathExpression} on the same terms.</p>
+ *
+ * <p>The {@code evaluateExpression} default methods added to the interface by Java 9 route through the {@code evaluate} overloads overridden here, so they
+ * carry the same rewrite on newer runtimes even though this class targets Java 8.</p>
+ */
+final class HardeningXPath implements XPath {
+
+ /**
+ * Parses the source through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder}, mirroring the namespace awareness of the parser the
+ * engine would have provisioned.
+ *
+ * @param source The document to evaluate against.
+ * @return The parsed document.
+ * @throws NullPointerException if {@code source} is {@code null}, per the {@link XPath} contract.
+ * @throws XPathExpressionException if the source cannot be parsed.
+ */
+ static Document parse(final InputSource source) throws XPathExpressionException {
+ if (source == null) {
+ throw new NullPointerException("source cannot be null");
+ }
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder().parse(source);
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new XPathExpressionException(e);
+ }
+ }
+
+ private final XPath delegate;
+
+ HardeningXPath(final XPath delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String evaluate(final String expression, final InputSource source) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source));
+ }
+
+ @Override
+ public Object evaluate(final String expression, final InputSource source, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source), returnType);
+ }
+
+ @Override
+ public XPathExpression compile(final String expression) throws XPathExpressionException {
+ final XPathExpression compiled = delegate.compile(expression);
+ return compiled == null ? null : new HardeningXPathExpression(compiled);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
Review Comment:
Doesn't Eclipse support this? IIRC there was an annotation that allowed grouping together methods that worked both on Eclipse and IntelliJ IDEA.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856400143
##########
src/test/java/org/apache/commons/xml/ShadingFootprintTest.java:
##########
@@ -69,23 +70,25 @@ class ShadingFootprintTest {
private static final Set<String> STAX_HARDENER = set("StaxHardener", "HardeningXMLInputFactory", "FallbackIgnoreXMLResolver", HARDENING_EXCEPTION);
/**
- * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#harden(Source)}, so each builds on the full SAX closure below.
+ * TrAX, XPath and schema re-harden their sub-parsers through {@link SAXParserHardener#hardenSource(Source)}, so each builds on the full SAX closure below; XPath
+ * additionally parses InputSource-taking evaluate calls through the DOM hardener, so its closure carries that set too.
*/
private static final Set<String> TRANSFORMER_HARDENER = saxParsersHardenerPlus("TransformerHardener", "HardeningTransformerFactory",
"HardeningTransformer", "HardeningTransformerHandler", "HardeningTemplates", "HardeningTemplatesHandler", "HardeningXMLFilter",
"FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1", "SaxonProvider$HardenedConfiguration"
, "SaxonProvider$SaxonProviderConfigurer");
- private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider", "SaxonProvider$1",
- "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer");
+ private static final Set<String> XPATH_HARDENER = saxParsersHardenerPlus("XPathHardener", "FallbackIgnoreURIResolver", "SaxonProvider",
+ "SaxonProvider$1", "SaxonProvider$HardenedConfiguration", "SaxonProvider$SaxonProviderConfigurer", "HardeningXPathFactory", "HardeningXPath",
+ "HardeningXPathExpression", "DocumentBuilderHardener", "HardeningDocumentBuilder", "HardeningDocumentBuilderFactory");
private static final Set<String> SCHEMA_HARDENER = saxParsersHardenerPlus("SchemaHardener", "HardeningSchemaFactory", "HardeningValidator",
"HardeningValidatorHandler", "HardeningSchema", "FallbackIgnoreLSResourceResolver");
/**
* Only the public {@link XmlFactories} entry, which news up every hardener, still pulls the whole library; this is its class count.
Review Comment:
Apparently it is short for “instantiates”. Fixed in https://github.com/apache/commons-xml/pull/44/commits/eb53959e3703f57263f3c349b3d1d74e94928d20
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3861833366
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor, while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ // Deterministic on every engine: the entity is declared in the internal subset and the floor resolves only its
+ // external content — to empty replacement text — so the pre-parse completes and the reference expands to nothing.
+ final String result = assertDoesNotThrow(
Review Comment:
Sure, go ahead.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "garydgregory (via GitHub)" <gi...@apache.org>.
garydgregory commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3861799615
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor, while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ // Deterministic on every engine: the entity is declared in the internal subset and the floor resolves only its
+ // external content — to empty replacement text — so the pre-parse completes and the reference expands to nothing.
+ final String result = assertDoesNotThrow(
Review Comment:
I'll probably remove all calls to `assertDoesNotThrow()` since all passing tests shouldn't throw.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856287724
##########
src/main/java/org/apache/commons/xml/HardeningXPath.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link XPath} wrapper that performs the document build behind every {@link InputSource}-taking {@code evaluate} call with a hardened, namespace-aware
+ * {@link javax.xml.parsers.DocumentBuilder} and evaluates the delegate against the parsed {@link Document}, so the engine's own parser never runs.
+ *
+ * <p>The JAXP contract for {@link XPath#evaluate(String, InputSource, QName)} is "build a document from the source, then evaluate against it", and both the
+ * stock JDK and Apache Xalan provision an internal parser for that build which {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the
+ * {@link javax.xml.xpath.XPathFactory} does not reach. Parsing here puts the build on the library's resolver floor: an external reference inside the document
+ * resolves to empty content, so it is neither fetched nor leaked, and the evaluation proceeds on whatever the parse produced. {@link #compile(String)} wraps
+ * the compiled expression in a {@link HardeningXPathExpression} on the same terms.</p>
+ *
+ * <p>The {@code evaluateExpression} default methods added to the interface by Java 9 route through the {@code evaluate} overloads overridden here, so they
+ * carry the same rewrite on newer runtimes even though this class targets Java 8.</p>
+ */
+final class HardeningXPath implements XPath {
+
+ /**
+ * Parses the source through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder}, mirroring the namespace awareness of the parser the
+ * engine would have provisioned.
+ *
+ * @param source The document to evaluate against.
+ * @return The parsed document.
+ * @throws NullPointerException if {@code source} is {@code null}, per the {@link XPath} contract.
+ * @throws XPathExpressionException if the source cannot be parsed.
+ */
+ static Document parse(final InputSource source) throws XPathExpressionException {
+ if (source == null) {
+ throw new NullPointerException("source cannot be null");
+ }
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder().parse(source);
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new XPathExpressionException(e);
+ }
+ }
+
+ private final XPath delegate;
+
+ HardeningXPath(final XPath delegate) {
+ this.delegate = delegate;
Review Comment:
I applied the suggested `Objects.requireNonNull` in https://github.com/apache/commons-xml/pull/44/commits/6315ef84d6731d2fc57e2cc6355e354a0db85738, but maybe we should use nullability annotations or comments instead, so we don't introduce a (cheap) runtime check even if no caller ever passes `null`>
##########
src/main/java/org/apache/commons/xml/HardeningXPathFactory.java:
##########
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathFactoryConfigurationException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+/**
+ * {@link XPathFactory} wrapper that returns a {@link HardeningXPath} from {@link #newXPath()}.
+ *
+ * <p>Required because {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the factory governs only the XPath engine: the stock JDK and Apache Xalan
+ * implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document parser the feature does not reach.
+ * The wrapper performs that document build itself through a hardened parser instead; see {@link HardeningXPath}.</p>
+ */
+final class HardeningXPathFactory extends XPathFactory {
+
+ private final XPathFactory delegate;
+
+ HardeningXPathFactory(final XPathFactory delegate) {
+ this.delegate = delegate;
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/6315ef84d6731d2fc57e2cc6355e354a0db85738
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3857580038
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/36b26a4c093f6669901c39728e287342af2202bb
##########
src/test/java/org/apache/commons/xml/XPathInputSourceTest.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that the document parse behind {@code XPath.evaluate(String, InputSource)} (and its compiled {@code XPathExpression} counterpart) cannot pull in an
+ * external general entity.
+ *
+ * <p>The stock JDK and Apache Xalan implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document
+ * parser that {@code FEATURE_SECURE_PROCESSING} on the {@link XPathFactory} does not reach. The {@link HardeningXPathFactory} wrapper parses the input
+ * through a hardened {@code DocumentBuilder} instead, so the external reference resolves to empty on the floor (or the parse is rejected outright), while the
+ * evaluation itself still works. Tagged {@code xpath}, so it runs under test-stockjdk, test-xalan and test-xalan-xerces; the Saxon engine takes the separate
+ * {@code SaxonProvider} path covered by {@code SaxonXPathExternalCallsTest}.</p>
+ */
+@Tag("xpath")
+class XPathInputSourceTest {
+
+ private static final String EXPRESSION = "string(/root/child)";
+
+ /** {@link AttackTestSupport#xmlBody} content whose single entity reference resolves to {@link AttackTestSupport#LEAKED_MARKER} if the DTD is fetched. */
+ private static String entityPayload() {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.txt") + "\">\n]>\n"
+ + AttackTestSupport.xmlBody("&xxe;");
+ }
+
+ @Test
+ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION, AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
+ }
+ }
+
+ @Test
+ void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
+ try {
+ final String result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the compiled XPath result: " + result);
+ } catch (final XPathExpressionException ignored) {
+ // Throwing is an acceptable result, since it does not leak the marker.
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/36b26a4c093f6669901c39728e287342af2202bb
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856266417
##########
src/main/java/org/apache/commons/xml/HardeningXPath.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link XPath} wrapper that performs the document build behind every {@link InputSource}-taking {@code evaluate} call with a hardened, namespace-aware
+ * {@link javax.xml.parsers.DocumentBuilder} and evaluates the delegate against the parsed {@link Document}, so the engine's own parser never runs.
+ *
+ * <p>The JAXP contract for {@link XPath#evaluate(String, InputSource, QName)} is "build a document from the source, then evaluate against it", and both the
+ * stock JDK and Apache Xalan provision an internal parser for that build which {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the
+ * {@link javax.xml.xpath.XPathFactory} does not reach. Parsing here puts the build on the library's resolver floor: an external reference inside the document
+ * resolves to empty content, so it is neither fetched nor leaked, and the evaluation proceeds on whatever the parse produced. {@link #compile(String)} wraps
+ * the compiled expression in a {@link HardeningXPathExpression} on the same terms.</p>
+ *
+ * <p>The {@code evaluateExpression} default methods added to the interface by Java 9 route through the {@code evaluate} overloads overridden here, so they
+ * carry the same rewrite on newer runtimes even though this class targets Java 8.</p>
+ */
+final class HardeningXPath implements XPath {
+
+ /**
+ * Parses the source through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder}, mirroring the namespace awareness of the parser the
+ * engine would have provisioned.
+ *
+ * @param source The document to evaluate against.
+ * @return The parsed document.
+ * @throws NullPointerException if {@code source} is {@code null}, per the {@link XPath} contract.
+ * @throws XPathExpressionException if the source cannot be parsed.
+ */
+ static Document parse(final InputSource source) throws XPathExpressionException {
+ if (source == null) {
+ throw new NullPointerException("source cannot be null");
+ }
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder().parse(source);
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new XPathExpressionException(e);
+ }
+ }
+
+ private final XPath delegate;
+
+ HardeningXPath(final XPath delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String evaluate(final String expression, final InputSource source) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source));
+ }
+
+ @Override
+ public Object evaluate(final String expression, final InputSource source, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source), returnType);
+ }
+
+ @Override
+ public XPathExpression compile(final String expression) throws XPathExpressionException {
+ final XPathExpression compiled = delegate.compile(expression);
+ return compiled == null ? null : new HardeningXPathExpression(compiled);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+ @Override
+ public String evaluate(final String expression, final Object item) throws XPathExpressionException {
+ return delegate.evaluate(expression, item);
+ }
+
+ @Override
+ public Object evaluate(final String expression, final Object item, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(expression, item, returnType);
+ }
+
+ @Override
+ public NamespaceContext getNamespaceContext() {
+ return delegate.getNamespaceContext();
+ }
+
+ @Override
+ public XPathFunctionResolver getXPathFunctionResolver() {
+ return delegate.getXPathFunctionResolver();
+ }
+
+ @Override
+ public XPathVariableResolver getXPathVariableResolver() {
+ return delegate.getXPathVariableResolver();
+ }
+
+ @Override
+ public void reset() {
+ delegate.reset();
+ }
+
+ @Override
+ public void setNamespaceContext(final NamespaceContext nsContext) {
+ delegate.setNamespaceContext(nsContext);
+ }
+
+ @Override
+ public void setXPathFunctionResolver(final XPathFunctionResolver resolver) {
+ delegate.setXPathFunctionResolver(resolver);
+ }
+
+ @Override
+ public void setXPathVariableResolver(final XPathVariableResolver resolver) {
+ delegate.setXPathVariableResolver(resolver);
+ }
+ // </editor-fold>
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/98125dc2ef0a2657554786aa72b4b36430fb11fc
##########
src/main/java/org/apache/commons/xml/HardeningXPath.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link XPath} wrapper that performs the document build behind every {@link InputSource}-taking {@code evaluate} call with a hardened, namespace-aware
+ * {@link javax.xml.parsers.DocumentBuilder} and evaluates the delegate against the parsed {@link Document}, so the engine's own parser never runs.
+ *
+ * <p>The JAXP contract for {@link XPath#evaluate(String, InputSource, QName)} is "build a document from the source, then evaluate against it", and both the
+ * stock JDK and Apache Xalan provision an internal parser for that build which {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the
+ * {@link javax.xml.xpath.XPathFactory} does not reach. Parsing here puts the build on the library's resolver floor: an external reference inside the document
+ * resolves to empty content, so it is neither fetched nor leaked, and the evaluation proceeds on whatever the parse produced. {@link #compile(String)} wraps
+ * the compiled expression in a {@link HardeningXPathExpression} on the same terms.</p>
+ *
+ * <p>The {@code evaluateExpression} default methods added to the interface by Java 9 route through the {@code evaluate} overloads overridden here, so they
+ * carry the same rewrite on newer runtimes even though this class targets Java 8.</p>
+ */
+final class HardeningXPath implements XPath {
+
+ /**
+ * Parses the source through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder}, mirroring the namespace awareness of the parser the
+ * engine would have provisioned.
+ *
+ * @param source The document to evaluate against.
+ * @return The parsed document.
+ * @throws NullPointerException if {@code source} is {@code null}, per the {@link XPath} contract.
+ * @throws XPathExpressionException if the source cannot be parsed.
+ */
+ static Document parse(final InputSource source) throws XPathExpressionException {
+ if (source == null) {
+ throw new NullPointerException("source cannot be null");
+ }
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder().parse(source);
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new XPathExpressionException(e);
+ }
+ }
+
+ private final XPath delegate;
+
+ HardeningXPath(final XPath delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String evaluate(final String expression, final InputSource source) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source));
+ }
+
+ @Override
+ public Object evaluate(final String expression, final InputSource source, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(expression, parse(source), returnType);
+ }
+
+ @Override
+ public XPathExpression compile(final String expression) throws XPathExpressionException {
+ final XPathExpression compiled = delegate.compile(expression);
+ return compiled == null ? null : new HardeningXPathExpression(compiled);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/98125dc2ef0a2657554786aa72b4b36430fb11fc
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Harden the XPath InputSource evaluation surface [commons-xml]
Posted by "ppkarwasz (via GitHub)" <gi...@apache.org>.
ppkarwasz commented on code in PR #44:
URL: https://github.com/apache/commons-xml/pull/44#discussion_r3856272437
##########
src/main/java/org/apache/commons/xml/HardeningXPathExpression.java:
##########
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+
+import org.xml.sax.InputSource;
+
+/**
+ * {@link XPathExpression} wrapper that applies the same {@link InputSource} rewrite as {@link HardeningXPath} to the compiled evaluation entry points.
+ *
+ * <p>{@link HardeningXPath#compile(String)} returns one of these, so {@link #evaluate(InputSource)} and {@link #evaluate(InputSource, QName)} build the
+ * document through a hardened, namespace-aware parser instead of the engine's own; the {@code evaluateExpression} default methods added by Java 9 route
+ * through these overloads as well.</p>
+ */
+final class HardeningXPathExpression implements XPathExpression {
+
+ private final XPathExpression delegate;
+
+ HardeningXPathExpression(final XPathExpression delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String evaluate(final InputSource source) throws XPathExpressionException {
+ return delegate.evaluate(HardeningXPath.parse(source));
+ }
+
+ @Override
+ public Object evaluate(final InputSource source, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(HardeningXPath.parse(source), returnType);
+ }
+
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+ @Override
+ public String evaluate(final Object item) throws XPathExpressionException {
+ return delegate.evaluate(item);
+ }
+
+ @Override
+ public Object evaluate(final Object item, final QName returnType) throws XPathExpressionException {
+ return delegate.evaluate(item, returnType);
+ }
+ // </editor-fold>
Review Comment:
Fixed in https://github.com/apache/commons-xml/pull/44/commits/98125dc2ef0a2657554786aa72b4b36430fb11fc
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@commons.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org