Skip to content

Commit

Permalink
CAMEL-10894: XML Validator: DTD Handling improved
Browse files Browse the repository at this point in the history
  • Loading branch information
Franz Forsthofer authored and stsiano committed Feb 24, 2017
1 parent 213a926 commit 2c6964a
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
};
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}

0 comments on commit 2c6964a

Please sign in to comment.