CVE-2018-8027
Description
Apache Camel 2.20.0 to 2.20.3 and 2.21.0 Core is vulnerable to XXE in XSD validation processor.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Camel 2.20.x and 2.21.0 are vulnerable to XXE injection through the XSD validation processor, potentially exposing sensitive data.
Vulnerability
CVE-2018-8027 is an XML External Entity (XXE) vulnerability in Apache Camel's XSD validation processor. It affects Apache Camel versions 2.20.0 through 2.20.3 and 2.21.0 [1]. The vulnerability exists in the SchemaReader class, which by default allows the processing of external Document Type Definitions (DTDs) when validating XML against a schema [2]. This configuration bypasses the Java XML parser's built-in protection against XXE attacks [3].
Exploitation
To exploit this vulnerability, an attacker must be able to supply a crafted XML payload containing an external entity reference that is processed by the Camel application's XSD validation routine [1]. The attack requires no special privileges; any service or route that uses the validator component with a schema and processes attacker-controlled XML is a potential target [2]. The attacker sends a malicious XML document that includes a reference to an external DTD or entity, which the Camel validator then resolves because it does not restrict external DTD access by default [3][4].
Impact
Successful exploitation leads to XML External Entity (XXE) injection, which can result in information disclosure [1]. An attacker can read arbitrary files on the server file system, perform server-side request forgery (SSRF) to access internal network resources, or cause denial of service via entity expansion [3][4]. The impact is limited to the privileges of the Java process running Apache Camel [2].
Mitigation
Apache Camel addressed this vulnerability in version 2.20.4 and 2.21.1 [1][2]. The fix introduces a global option CamelXmlValidatorAccessExternalDTD (defaulting to false) that disables external DTD processing unless explicitly enabled [3]. Users should upgrade to the patched versions immediately. If upgrading is not immediately possible, a workaround is to configure the XML parser manually to disallow external DTD access through JVM-level security properties or the SchemaFactory [4]. This CVE is not listed in the Known Exploited Vulnerabilities (KEV) catalog.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.camel:camel-coreMaven | >= 2.20.0, < 2.20.4 | 2.20.4 |
org.apache.camel:camel-coreMaven | >= 2.21.0, < 2.21.1 | 2.21.1 |
Affected products
2- Apache Software Foundation/Apache Camelv5Range: 2.20.0 to 2.20.3
Patches
2424eefa559fe6CAMEL-12444: Improved DTD handling in validator component.
2 files changed · +14 −0
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −0 modified@@ -175,6 +175,7 @@ protected SchemaFactory createSchemaFactory() { } if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { + LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) { LOG.warn(e.getMessage(), e);
camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java+13 −0 modified@@ -22,6 +22,7 @@ import java.net.URL; import java.util.Collections; +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -53,6 +54,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.processor.validation.SchemaReader.ACCESS_EXTERNAL_DTD; + /** * A processor which validates the XML version of the inbound message body * against some schema either in XSD or RelaxNG @@ -100,6 +103,16 @@ protected void doProcess(Exchange exchange) throws Exception { } Validator validator = schema.newValidator(); + // turn off access to external schema by default + if (!Boolean.parseBoolean(exchange.getContext().getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + LOG.debug("Configuring Validator to not allow access to external DTD/Schema"); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXException e) { + LOG.warn(e.getMessage(), e); + } + } // the underlying input stream, which we need to close to avoid locking files or other resources Source source = null;
24eefa559fe6CAMEL-12444: Improved DTD handling in validator component.
2 files changed · +14 −0
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −0 modified@@ -175,6 +175,7 @@ protected SchemaFactory createSchemaFactory() { } if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { + LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) { LOG.warn(e.getMessage(), e);
camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java+13 −0 modified@@ -22,6 +22,7 @@ import java.net.URL; import java.util.Collections; +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -53,6 +54,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.processor.validation.SchemaReader.ACCESS_EXTERNAL_DTD; + /** * A processor which validates the XML version of the inbound message body * against some schema either in XSD or RelaxNG @@ -100,6 +103,16 @@ protected void doProcess(Exchange exchange) throws Exception { } Validator validator = schema.newValidator(); + // turn off access to external schema by default + if (!Boolean.parseBoolean(exchange.getContext().getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + LOG.debug("Configuring Validator to not allow access to external DTD/Schema"); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXException e) { + LOG.warn(e.getMessage(), e); + } + } // the underlying input stream, which we need to close to avoid locking files or other resources Source source = null;
8467d644813aCAMEL-12444: Improved DTD handling in validator component.
2 files changed · +14 −0
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −0 modified@@ -175,6 +175,7 @@ protected SchemaFactory createSchemaFactory() { } if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { + LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) { LOG.warn(e.getMessage(), e);
camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java+13 −0 modified@@ -22,6 +22,7 @@ import java.net.URL; import java.util.Collections; +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -53,6 +54,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.processor.validation.SchemaReader.ACCESS_EXTERNAL_DTD; + /** * A processor which validates the XML version of the inbound message body * against some schema either in XSD or RelaxNG @@ -100,6 +103,16 @@ protected void doProcess(Exchange exchange) throws Exception { } Validator validator = schema.newValidator(); + // turn off access to external schema by default + if (!Boolean.parseBoolean(exchange.getContext().getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + LOG.debug("Configuring Validator to not allow access to external DTD/Schema"); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXException e) { + LOG.warn(e.getMessage(), e); + } + } // the underlying input stream, which we need to close to avoid locking files or other resources Source source = null;
8467d644813aCAMEL-12444: Improved DTD handling in validator component.
2 files changed · +14 −0
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −0 modified@@ -175,6 +175,7 @@ protected SchemaFactory createSchemaFactory() { } if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { + LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) { LOG.warn(e.getMessage(), e);
camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java+13 −0 modified@@ -22,6 +22,7 @@ import java.net.URL; import java.util.Collections; +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -53,6 +54,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.processor.validation.SchemaReader.ACCESS_EXTERNAL_DTD; + /** * A processor which validates the XML version of the inbound message body * against some schema either in XSD or RelaxNG @@ -100,6 +103,16 @@ protected void doProcess(Exchange exchange) throws Exception { } Validator validator = schema.newValidator(); + // turn off access to external schema by default + if (!Boolean.parseBoolean(exchange.getContext().getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + LOG.debug("Configuring Validator to not allow access to external DTD/Schema"); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXException e) { + LOG.warn(e.getMessage(), e); + } + } // the underlying input stream, which we need to close to avoid locking files or other resources Source source = null;
3fe03e361725CAMEL-12444: Improved DTD handling in validator component.
2 files changed · +14 −0
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −0 modified@@ -175,6 +175,7 @@ protected SchemaFactory createSchemaFactory() { } if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { + LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) { LOG.warn(e.getMessage(), e);
camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java+13 −0 modified@@ -22,6 +22,7 @@ import java.net.URL; import java.util.Collections; +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -53,6 +54,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.processor.validation.SchemaReader.ACCESS_EXTERNAL_DTD; + /** * A processor which validates the XML version of the inbound message body * against some schema either in XSD or RelaxNG @@ -100,6 +103,16 @@ protected void doProcess(Exchange exchange) throws Exception { } Validator validator = schema.newValidator(); + // turn off access to external schema by default + if (!Boolean.parseBoolean(exchange.getContext().getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + LOG.debug("Configuring Validator to not allow access to external DTD/Schema"); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXException e) { + LOG.warn(e.getMessage(), e); + } + } // the underlying input stream, which we need to close to avoid locking files or other resources Source source = null;
3fe03e361725CAMEL-12444: Improved DTD handling in validator component.
2 files changed · +14 −0
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −0 modified@@ -175,6 +175,7 @@ protected SchemaFactory createSchemaFactory() { } if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { + LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) { LOG.warn(e.getMessage(), e);
camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java+13 −0 modified@@ -22,6 +22,7 @@ import java.net.URL; import java.util.Collections; +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -53,6 +54,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.processor.validation.SchemaReader.ACCESS_EXTERNAL_DTD; + /** * A processor which validates the XML version of the inbound message body * against some schema either in XSD or RelaxNG @@ -100,6 +103,16 @@ protected void doProcess(Exchange exchange) throws Exception { } Validator validator = schema.newValidator(); + // turn off access to external schema by default + if (!Boolean.parseBoolean(exchange.getContext().getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + LOG.debug("Configuring Validator to not allow access to external DTD/Schema"); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXException e) { + LOG.warn(e.getMessage(), e); + } + } // the underlying input stream, which we need to close to avoid locking files or other resources Source source = null;
99cbcd78b7e6CAMEL-10894: Improve test fix
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext == null || !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
99cbcd78b7e6CAMEL-10894: Improve test fix
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext == null || !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
ec3d0db81ba0CAMEL-10894: Improve test fix
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext == null || !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
9c6a8f61de40CAMEL-10894: Improve test fix
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (camelContext != null && !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
9c6a8f61de40CAMEL-10894: Improve test fix
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (camelContext != null && !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
ec3d0db81ba0CAMEL-10894: Improve test fix
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext == null || !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
22c355bb4ffbCAMEL-10894 Fixed test failure
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
2e8f21dec883CAMEL-10894 Fixed test failure
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (!Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + if (camelContext != null && !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
22c355bb4ffbCAMEL-10894 Fixed test failure
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
87c92b7b3889CAMEL-10894 Fixed test failure
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
2e8f21dec883CAMEL-10894 Fixed test failure
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (!Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + if (camelContext != null && !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
87c92b7b3889CAMEL-10894 Fixed test failure
1 file changed · +1 −1
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+1 −1 modified@@ -173,7 +173,7 @@ protected SchemaFactory createSchemaFactory() { if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); } - if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + if (camelContext != null && !Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { try { factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (SAXException e) {
2c6964ae94d8CAMEL-10894: XML Validator: DTD Handling improved
4 files changed · +223 −4
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+15 −4 modified@@ -30,7 +30,6 @@ import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.SAXException; - import org.apache.camel.CamelContext; import org.apache.camel.converter.IOConverter; import org.apache.camel.util.IOHelper; @@ -41,13 +40,17 @@ import org.slf4j.LoggerFactory; /** - * Reads the schema used in the processor {@link ValidatingProcessor}. Contains - * the method {@link clearCachedSchema()} to force re-reading the schema. + * Reads the schema used in the processor {@link ValidatingProcessor}. + * A schema re-reading could be forced using {@link org.apache.camel.component.validator.ValidatorEndpoint#clearCachedSchema()}. */ public class SchemaReader { + /** Key of the global option to switch either off or on the access to external DTDs in the XML Validator for StreamSources. + * Only effective, if not a custom schema factory is used.*/ + public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD"; + private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class); - + private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema private volatile Schema schema; @@ -169,6 +172,14 @@ protected SchemaFactory createSchemaFactory() { SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage); if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); + } + if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + try { + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (SAXException e) { + LOG.error(e.getMessage(), e); + throw new IllegalStateException(e); + } } return factory; }
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessAbstractTest.java+86 −0 added@@ -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 + * + * http://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.camel.component.validator; + +import java.net.UnknownHostException; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ValidationException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.processor.validation.SchemaReader; + +public abstract class ValidatorDtdAccessAbstractTest extends ContextTestSupport { + + protected MockEndpoint finallyEndpoint; + protected MockEndpoint invalidEndpoint; + protected MockEndpoint unknownHostExceptionEndpoint; + protected MockEndpoint validEndpoint; + + protected String payloud = getPayloudPart("Hello world!"); + + protected String ssrfPayloud = "<!DOCTYPE roottag PUBLIC \"-//VSR//PENTEST//EN\" \"http://notexisting/test\">\n" + payloud; + + protected String xxePayloud = "<!DOCTYPE updateProfile [<!ENTITY file SYSTEM \"http://notexistinghost/test\">]>\n" + getPayloudPart("&file;"); + + private final boolean accessExternalDTD; + + public ValidatorDtdAccessAbstractTest(boolean accessExternalDTD) { + this.accessExternalDTD = accessExternalDTD; + } + + + private String getPayloudPart(String bodyValue) { + return "<mail xmlns='http://foo.com/bar'><subject>Hey</subject><body>" + bodyValue + "</body></mail>"; + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validEndpoint = resolveMandatoryEndpoint("mock:valid", MockEndpoint.class); + invalidEndpoint = resolveMandatoryEndpoint("mock:invalid", MockEndpoint.class); + unknownHostExceptionEndpoint = resolveMandatoryEndpoint("mock:unknownHostException", MockEndpoint.class); + finallyEndpoint = resolveMandatoryEndpoint("mock:finally", MockEndpoint.class); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + // switch on DTD Access + if (accessExternalDTD) { + getContext().getProperties().put(SchemaReader.ACCESS_EXTERNAL_DTD, "true"); + } + from("direct:start") + .doTry() + .to("validator:org/apache/camel/component/validator/schema.xsd") + .to("mock:valid") + .doCatch(ValidationException.class) + .to("mock:invalid") + .doCatch(UnknownHostException.class) + .to("mock:unknownHostException") + .doFinally() + .to("mock:finally").end(); + } + }; + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOffTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOffTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOffTest() { + super(false); + } + + /** Tests that no external DTD call is executed for StringSource. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is not executed for StreamSource. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is not possible for StreamSource. */ + public void testInvalidMessageXXESourceStream() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOnTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOnTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOnTest() { + super(true); + } + + /** Tests that external DTD call is executed for StringSource by expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is executed for StreamSourceby expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is possible for StreamSource by expecting an UnkonwHostException. */ + public void testInvalidMessageXXESourceStream() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
9f7376abbff7CAMEL-10894: XML Validator: DTD Handling improved
4 files changed · +223 −4
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+15 −4 modified@@ -30,7 +30,6 @@ import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.SAXException; - import org.apache.camel.CamelContext; import org.apache.camel.converter.IOConverter; import org.apache.camel.util.IOHelper; @@ -41,13 +40,17 @@ import org.slf4j.LoggerFactory; /** - * Reads the schema used in the processor {@link ValidatingProcessor}. Contains - * the method {@link clearCachedSchema()} to force re-reading the schema. + * Reads the schema used in the processor {@link ValidatingProcessor}. + * A schema re-reading could be forced using {@link org.apache.camel.component.validator.ValidatorEndpoint#clearCachedSchema()}. */ public class SchemaReader { + /** Key of the global option to switch either off or on the access to external DTDs in the XML Validator for StreamSources. + * Only effective, if not a custom schema factory is used.*/ + public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD"; + private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class); - + private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema private volatile Schema schema; @@ -169,6 +172,14 @@ protected SchemaFactory createSchemaFactory() { SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage); if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); + } + if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + try { + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (SAXException e) { + LOG.error(e.getMessage(), e); + throw new IllegalStateException(e); + } } return factory; }
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessAbstractTest.java+86 −0 added@@ -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 + * + * http://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.camel.component.validator; + +import java.net.UnknownHostException; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ValidationException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.processor.validation.SchemaReader; + +public abstract class ValidatorDtdAccessAbstractTest extends ContextTestSupport { + + protected MockEndpoint finallyEndpoint; + protected MockEndpoint invalidEndpoint; + protected MockEndpoint unknownHostExceptionEndpoint; + protected MockEndpoint validEndpoint; + + protected String payloud = getPayloudPart("Hello world!"); + + protected String ssrfPayloud = "<!DOCTYPE roottag PUBLIC \"-//VSR//PENTEST//EN\" \"http://notexisting/test\">\n" + payloud; + + protected String xxePayloud = "<!DOCTYPE updateProfile [<!ENTITY file SYSTEM \"http://notexistinghost/test\">]>\n" + getPayloudPart("&file;"); + + private final boolean accessExternalDTD; + + public ValidatorDtdAccessAbstractTest(boolean accessExternalDTD) { + this.accessExternalDTD = accessExternalDTD; + } + + + private String getPayloudPart(String bodyValue) { + return "<mail xmlns='http://foo.com/bar'><subject>Hey</subject><body>" + bodyValue + "</body></mail>"; + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validEndpoint = resolveMandatoryEndpoint("mock:valid", MockEndpoint.class); + invalidEndpoint = resolveMandatoryEndpoint("mock:invalid", MockEndpoint.class); + unknownHostExceptionEndpoint = resolveMandatoryEndpoint("mock:unknownHostException", MockEndpoint.class); + finallyEndpoint = resolveMandatoryEndpoint("mock:finally", MockEndpoint.class); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + // switch on DTD Access + if (accessExternalDTD) { + getContext().getProperties().put(SchemaReader.ACCESS_EXTERNAL_DTD, "true"); + } + from("direct:start") + .doTry() + .to("validator:org/apache/camel/component/validator/schema.xsd") + .to("mock:valid") + .doCatch(ValidationException.class) + .to("mock:invalid") + .doCatch(UnknownHostException.class) + .to("mock:unknownHostException") + .doFinally() + .to("mock:finally").end(); + } + }; + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOffTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOffTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOffTest() { + super(false); + } + + /** Tests that no external DTD call is executed for StringSource. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is not executed for StreamSource. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is not possible for StreamSource. */ + public void testInvalidMessageXXESourceStream() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOnTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOnTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOnTest() { + super(true); + } + + /** Tests that external DTD call is executed for StringSource by expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is executed for StreamSourceby expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is possible for StreamSource by expecting an UnkonwHostException. */ + public void testInvalidMessageXXESourceStream() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
2c6964ae94d8CAMEL-10894: XML Validator: DTD Handling improved
4 files changed · +223 −4
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+15 −4 modified@@ -30,7 +30,6 @@ import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.SAXException; - import org.apache.camel.CamelContext; import org.apache.camel.converter.IOConverter; import org.apache.camel.util.IOHelper; @@ -41,13 +40,17 @@ import org.slf4j.LoggerFactory; /** - * Reads the schema used in the processor {@link ValidatingProcessor}. Contains - * the method {@link clearCachedSchema()} to force re-reading the schema. + * Reads the schema used in the processor {@link ValidatingProcessor}. + * A schema re-reading could be forced using {@link org.apache.camel.component.validator.ValidatorEndpoint#clearCachedSchema()}. */ public class SchemaReader { + /** Key of the global option to switch either off or on the access to external DTDs in the XML Validator for StreamSources. + * Only effective, if not a custom schema factory is used.*/ + public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD"; + private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class); - + private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema private volatile Schema schema; @@ -169,6 +172,14 @@ protected SchemaFactory createSchemaFactory() { SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage); if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); + } + if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + try { + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (SAXException e) { + LOG.error(e.getMessage(), e); + throw new IllegalStateException(e); + } } return factory; }
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessAbstractTest.java+86 −0 added@@ -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 + * + * http://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.camel.component.validator; + +import java.net.UnknownHostException; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ValidationException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.processor.validation.SchemaReader; + +public abstract class ValidatorDtdAccessAbstractTest extends ContextTestSupport { + + protected MockEndpoint finallyEndpoint; + protected MockEndpoint invalidEndpoint; + protected MockEndpoint unknownHostExceptionEndpoint; + protected MockEndpoint validEndpoint; + + protected String payloud = getPayloudPart("Hello world!"); + + protected String ssrfPayloud = "<!DOCTYPE roottag PUBLIC \"-//VSR//PENTEST//EN\" \"http://notexisting/test\">\n" + payloud; + + protected String xxePayloud = "<!DOCTYPE updateProfile [<!ENTITY file SYSTEM \"http://notexistinghost/test\">]>\n" + getPayloudPart("&file;"); + + private final boolean accessExternalDTD; + + public ValidatorDtdAccessAbstractTest(boolean accessExternalDTD) { + this.accessExternalDTD = accessExternalDTD; + } + + + private String getPayloudPart(String bodyValue) { + return "<mail xmlns='http://foo.com/bar'><subject>Hey</subject><body>" + bodyValue + "</body></mail>"; + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validEndpoint = resolveMandatoryEndpoint("mock:valid", MockEndpoint.class); + invalidEndpoint = resolveMandatoryEndpoint("mock:invalid", MockEndpoint.class); + unknownHostExceptionEndpoint = resolveMandatoryEndpoint("mock:unknownHostException", MockEndpoint.class); + finallyEndpoint = resolveMandatoryEndpoint("mock:finally", MockEndpoint.class); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + // switch on DTD Access + if (accessExternalDTD) { + getContext().getProperties().put(SchemaReader.ACCESS_EXTERNAL_DTD, "true"); + } + from("direct:start") + .doTry() + .to("validator:org/apache/camel/component/validator/schema.xsd") + .to("mock:valid") + .doCatch(ValidationException.class) + .to("mock:invalid") + .doCatch(UnknownHostException.class) + .to("mock:unknownHostException") + .doFinally() + .to("mock:finally").end(); + } + }; + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOffTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOffTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOffTest() { + super(false); + } + + /** Tests that no external DTD call is executed for StringSource. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is not executed for StreamSource. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is not possible for StreamSource. */ + public void testInvalidMessageXXESourceStream() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOnTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOnTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOnTest() { + super(true); + } + + /** Tests that external DTD call is executed for StringSource by expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is executed for StreamSourceby expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is possible for StreamSource by expecting an UnkonwHostException. */ + public void testInvalidMessageXXESourceStream() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
9f7376abbff7CAMEL-10894: XML Validator: DTD Handling improved
4 files changed · +223 −4
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+15 −4 modified@@ -30,7 +30,6 @@ import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.SAXException; - import org.apache.camel.CamelContext; import org.apache.camel.converter.IOConverter; import org.apache.camel.util.IOHelper; @@ -41,13 +40,17 @@ import org.slf4j.LoggerFactory; /** - * Reads the schema used in the processor {@link ValidatingProcessor}. Contains - * the method {@link clearCachedSchema()} to force re-reading the schema. + * Reads the schema used in the processor {@link ValidatingProcessor}. + * A schema re-reading could be forced using {@link org.apache.camel.component.validator.ValidatorEndpoint#clearCachedSchema()}. */ public class SchemaReader { + /** Key of the global option to switch either off or on the access to external DTDs in the XML Validator for StreamSources. + * Only effective, if not a custom schema factory is used.*/ + public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD"; + private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class); - + private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema private volatile Schema schema; @@ -169,6 +172,14 @@ protected SchemaFactory createSchemaFactory() { SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage); if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); + } + if (!Boolean.parseBoolean(camelContext.getProperty(ACCESS_EXTERNAL_DTD))) { + try { + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (SAXException e) { + LOG.error(e.getMessage(), e); + throw new IllegalStateException(e); + } } return factory; }
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessAbstractTest.java+86 −0 added@@ -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 + * + * http://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.camel.component.validator; + +import java.net.UnknownHostException; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ValidationException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.processor.validation.SchemaReader; + +public abstract class ValidatorDtdAccessAbstractTest extends ContextTestSupport { + + protected MockEndpoint finallyEndpoint; + protected MockEndpoint invalidEndpoint; + protected MockEndpoint unknownHostExceptionEndpoint; + protected MockEndpoint validEndpoint; + + protected String payloud = getPayloudPart("Hello world!"); + + protected String ssrfPayloud = "<!DOCTYPE roottag PUBLIC \"-//VSR//PENTEST//EN\" \"http://notexisting/test\">\n" + payloud; + + protected String xxePayloud = "<!DOCTYPE updateProfile [<!ENTITY file SYSTEM \"http://notexistinghost/test\">]>\n" + getPayloudPart("&file;"); + + private final boolean accessExternalDTD; + + public ValidatorDtdAccessAbstractTest(boolean accessExternalDTD) { + this.accessExternalDTD = accessExternalDTD; + } + + + private String getPayloudPart(String bodyValue) { + return "<mail xmlns='http://foo.com/bar'><subject>Hey</subject><body>" + bodyValue + "</body></mail>"; + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validEndpoint = resolveMandatoryEndpoint("mock:valid", MockEndpoint.class); + invalidEndpoint = resolveMandatoryEndpoint("mock:invalid", MockEndpoint.class); + unknownHostExceptionEndpoint = resolveMandatoryEndpoint("mock:unknownHostException", MockEndpoint.class); + finallyEndpoint = resolveMandatoryEndpoint("mock:finally", MockEndpoint.class); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + // switch on DTD Access + if (accessExternalDTD) { + getContext().getProperties().put(SchemaReader.ACCESS_EXTERNAL_DTD, "true"); + } + from("direct:start") + .doTry() + .to("validator:org/apache/camel/component/validator/schema.xsd") + .to("mock:valid") + .doCatch(ValidationException.class) + .to("mock:invalid") + .doCatch(UnknownHostException.class) + .to("mock:unknownHostException") + .doFinally() + .to("mock:finally").end(); + } + }; + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOffTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOffTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOffTest() { + super(false); + } + + /** Tests that no external DTD call is executed for StringSource. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is not executed for StreamSource. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is not possible for StreamSource. */ + public void testInvalidMessageXXESourceStream() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOnTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOnTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOnTest() { + super(true); + } + + /** Tests that external DTD call is executed for StringSource by expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is executed for StreamSourceby expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is possible for StreamSource by expecting an UnkonwHostException. */ + public void testInvalidMessageXXESourceStream() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
8afc5d175779CAMEL-10894: DTD handling in the XML Validator corrected
4 files changed · +221 −2
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+13 −2 modified@@ -30,7 +30,6 @@ import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.SAXException; - import org.apache.camel.CamelContext; import org.apache.camel.converter.IOConverter; import org.apache.camel.util.IOHelper; @@ -46,8 +45,12 @@ */ public class SchemaReader { + /** Key of the global option to switch either off or on the access to external DTDs in the XML Validator for StreamSources. + * Only effective, if not a custom schema factory is used.*/ + public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD"; + private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class); - + private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema private volatile Schema schema; @@ -169,6 +172,14 @@ protected SchemaFactory createSchemaFactory() { SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage); if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); + } + if (!Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (SAXException e) { + LOG.error(e.getMessage(), e); + throw new IllegalStateException(e); + } } return factory; }
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessAbstractTest.java+86 −0 added@@ -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 + * + * http://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.camel.component.validator; + +import java.net.UnknownHostException; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ValidationException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.processor.validation.SchemaReader; + +public abstract class ValidatorDtdAccessAbstractTest extends ContextTestSupport { + + protected MockEndpoint finallyEndpoint; + protected MockEndpoint invalidEndpoint; + protected MockEndpoint unknownHostExceptionEndpoint; + protected MockEndpoint validEndpoint; + + protected String payloud = getPayloudPart("Hello world!"); + + protected String ssrfPayloud = "<!DOCTYPE roottag PUBLIC \"-//VSR//PENTEST//EN\" \"http://notexisting/test\">\n" + payloud; + + protected String xxePayloud = "<!DOCTYPE updateProfile [<!ENTITY file SYSTEM \"http://notexistinghost/test\">]>\n" + getPayloudPart("&file;"); + + private final boolean accessExternalDTD; + + public ValidatorDtdAccessAbstractTest(boolean accessExternalDTD) { + this.accessExternalDTD = accessExternalDTD; + } + + + private String getPayloudPart(String bodyValue) { + return "<mail xmlns='http://foo.com/bar'><subject>Hey</subject><body>" + bodyValue + "</body></mail>"; + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validEndpoint = resolveMandatoryEndpoint("mock:valid", MockEndpoint.class); + invalidEndpoint = resolveMandatoryEndpoint("mock:invalid", MockEndpoint.class); + unknownHostExceptionEndpoint = resolveMandatoryEndpoint("mock:unknownHostException", MockEndpoint.class); + finallyEndpoint = resolveMandatoryEndpoint("mock:finally", MockEndpoint.class); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + // switch on DTD Access + if (accessExternalDTD) { + getContext().getGlobalOptions().put(SchemaReader.ACCESS_EXTERNAL_DTD, "true"); + } + from("direct:start") + .doTry() + .to("validator:org/apache/camel/component/validator/schema.xsd") + .to("mock:valid") + .doCatch(ValidationException.class) + .to("mock:invalid") + .doCatch(UnknownHostException.class) + .to("mock:unknownHostException") + .doFinally() + .to("mock:finally").end(); + } + }; + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOffTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOffTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOffTest() { + super(false); + } + + /** Tests that no external DTD call is executed for StringSource. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is not executed for StreamSource. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is not possible for StreamSource. */ + public void testInvalidMessageXXESourceStream() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOnTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOnTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOnTest() { + super(true); + } + + /** Tests that external DTD call is executed for StringSource by expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is executed for StreamSourceby expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is possible for StreamSource by expecting an UnkonwHostException. */ + public void testInvalidMessageXXESourceStream() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
8afc5d175779CAMEL-10894: DTD handling in the XML Validator corrected
4 files changed · +221 −2
camel-core/src/main/java/org/apache/camel/processor/validation/SchemaReader.java+13 −2 modified@@ -30,7 +30,6 @@ import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.SAXException; - import org.apache.camel.CamelContext; import org.apache.camel.converter.IOConverter; import org.apache.camel.util.IOHelper; @@ -46,8 +45,12 @@ */ public class SchemaReader { + /** Key of the global option to switch either off or on the access to external DTDs in the XML Validator for StreamSources. + * Only effective, if not a custom schema factory is used.*/ + public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD"; + private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class); - + private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema private volatile Schema schema; @@ -169,6 +172,14 @@ protected SchemaFactory createSchemaFactory() { SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage); if (getResourceResolver() != null) { factory.setResourceResolver(getResourceResolver()); + } + if (!Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { + try { + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (SAXException e) { + LOG.error(e.getMessage(), e); + throw new IllegalStateException(e); + } } return factory; }
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessAbstractTest.java+86 −0 added@@ -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 + * + * http://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.camel.component.validator; + +import java.net.UnknownHostException; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ValidationException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.processor.validation.SchemaReader; + +public abstract class ValidatorDtdAccessAbstractTest extends ContextTestSupport { + + protected MockEndpoint finallyEndpoint; + protected MockEndpoint invalidEndpoint; + protected MockEndpoint unknownHostExceptionEndpoint; + protected MockEndpoint validEndpoint; + + protected String payloud = getPayloudPart("Hello world!"); + + protected String ssrfPayloud = "<!DOCTYPE roottag PUBLIC \"-//VSR//PENTEST//EN\" \"http://notexisting/test\">\n" + payloud; + + protected String xxePayloud = "<!DOCTYPE updateProfile [<!ENTITY file SYSTEM \"http://notexistinghost/test\">]>\n" + getPayloudPart("&file;"); + + private final boolean accessExternalDTD; + + public ValidatorDtdAccessAbstractTest(boolean accessExternalDTD) { + this.accessExternalDTD = accessExternalDTD; + } + + + private String getPayloudPart(String bodyValue) { + return "<mail xmlns='http://foo.com/bar'><subject>Hey</subject><body>" + bodyValue + "</body></mail>"; + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + validEndpoint = resolveMandatoryEndpoint("mock:valid", MockEndpoint.class); + invalidEndpoint = resolveMandatoryEndpoint("mock:invalid", MockEndpoint.class); + unknownHostExceptionEndpoint = resolveMandatoryEndpoint("mock:unknownHostException", MockEndpoint.class); + finallyEndpoint = resolveMandatoryEndpoint("mock:finally", MockEndpoint.class); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + // switch on DTD Access + if (accessExternalDTD) { + getContext().getGlobalOptions().put(SchemaReader.ACCESS_EXTERNAL_DTD, "true"); + } + from("direct:start") + .doTry() + .to("validator:org/apache/camel/component/validator/schema.xsd") + .to("mock:valid") + .doCatch(ValidationException.class) + .to("mock:invalid") + .doCatch(UnknownHostException.class) + .to("mock:unknownHostException") + .doFinally() + .to("mock:finally").end(); + } + }; + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOffTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOffTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOffTest() { + super(false); + } + + /** Tests that no external DTD call is executed for StringSource. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is not executed for StreamSource. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is not possible for StreamSource. */ + public void testInvalidMessageXXESourceStream() throws Exception { + invalidEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
camel-core/src/test/java/org/apache/camel/component/validator/ValidatorDtdAccessOnTest.java+61 −0 added@@ -0,0 +1,61 @@ +/** + * 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 + * + * http://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.camel.component.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.mock.MockEndpoint; + +public class ValidatorDtdAccessOnTest extends ValidatorDtdAccessAbstractTest { + + public ValidatorDtdAccessOnTest() { + super(true); + } + + /** Tests that external DTD call is executed for StringSource by expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStringSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + + template.sendBody("direct:start", ssrfPayloud); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that external DTD call is executed for StreamSourceby expecting an UnkonwHostException. */ + public void testInvalidMessageWithExternalDTDStreamSource() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(ssrfPayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + + /** Tests that XXE is possible for StreamSource by expecting an UnkonwHostException. */ + public void testInvalidMessageXXESourceStream() throws Exception { + unknownHostExceptionEndpoint.expectedMessageCount(1); + finallyEndpoint.expectedMessageCount(1); + InputStream is = new ByteArrayInputStream(xxePayloud.getBytes(StandardCharsets.UTF_8)); + template.sendBody("direct:start", is); + + MockEndpoint.assertIsSatisfied(validEndpoint, unknownHostExceptionEndpoint, finallyEndpoint); + } + +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
24- github.com/advisories/GHSA-8vfm-4388-6rpcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-8027ghsaADVISORY
- camel.apache.org/security-advisories.data/CVE-2018-8027.txt.ascghsax_refsource_CONFIRMWEB
- www.securityfocus.com/bid/104933mitrevdb-entryx_refsource_BID
- github.com/apache/camel/commit/22c355bb4ffb500405499d189db30932ca5aac9ghsaWEB
- github.com/apache/camel/commit/24eefa559fe6b310629d2bf00663d2679ec81b9ghsaWEB
- github.com/apache/camel/commit/2c6964ae94d8f9a9c9a32e5ae5a0b794e8b8d3bghsaWEB
- github.com/apache/camel/commit/2e8f21dec883b083ddcdddd802847b4c378a61aghsaWEB
- github.com/apache/camel/commit/3fe03e361725b66c1c3eaa40bb11577fb3dc17bghsaWEB
- github.com/apache/camel/commit/8467d644813a62f3a836c0c7dee8cf5a41de3c0ghsaWEB
- github.com/apache/camel/commit/87c92b7b38890c217bc76f2c55036e6a5cca9a0ghsaWEB
- github.com/apache/camel/commit/8afc5d1757795fde715902067360af5d90f046dghsaWEB
- github.com/apache/camel/commit/99cbcd78b7e64083fae1d9552ead7425a90994bghsaWEB
- github.com/apache/camel/commit/9c6a8f61de40c20f28240fbb2af4cb425793d41ghsaWEB
- github.com/apache/camel/commit/9f7376abbff7434794f2c7c2909e02bac232fb5ghsaWEB
- github.com/apache/camel/commit/ec3d0db81ba061b27e934d5ff56e9baca0049ebghsaWEB
- issues.apache.org/jira/browse/CAMEL-10894ghsaWEB
- issues.apache.org/jira/browse/CAMEL-12444ghsaWEB
- lists.apache.org/thread.html/2318d7f7d87724d8716cd650c21b31cb06e4d34f6d0f5ee42f28fdaf%40%3Ccommits.camel.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/2318d7f7d87724d8716cd650c21b31cb06e4d34f6d0f5ee42f28fdaf@%3Ccommits.camel.apache.org%3EghsaWEB
- lists.apache.org/thread.html/77f596fc63e63c2e9adcff3c34759b32c225cf0b582aedb755adaade%40%3Cdev.camel.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/77f596fc63e63c2e9adcff3c34759b32c225cf0b582aedb755adaade@%3Cdev.camel.apache.org%3EghsaWEB
- lists.apache.org/thread.html/b4014ea7c5830ca1fc28edd5cafedfe93ad4af2d9e69c961c5def31d%40%3Ccommits.camel.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/b4014ea7c5830ca1fc28edd5cafedfe93ad4af2d9e69c961c5def31d@%3Ccommits.camel.apache.org%3EghsaWEB
News mentions
0No linked articles in our index yet.