Merge branch 'branch-1.1.x'

# Conflicts:
#	src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java
This commit is contained in:
Andreas Penski (init) 2020-02-17 16:50:49 +01:00
commit 1807c78c51
3 changed files with 85 additions and 57 deletions

View file

@ -34,7 +34,12 @@ import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*; import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.validation.Schema; import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory; import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator; import javax.xml.validation.Validator;
@ -47,7 +52,11 @@ import lombok.extern.slf4j.Slf4j;
import net.sf.saxon.Configuration; import net.sf.saxon.Configuration;
import net.sf.saxon.expr.XPathContext; import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.*; import net.sf.saxon.lib.CollectionFinder;
import net.sf.saxon.lib.FeatureKeys;
import net.sf.saxon.lib.OutputURIResolver;
import net.sf.saxon.lib.ResourceCollection;
import net.sf.saxon.lib.UnparsedTextURIResolver;
import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.Processor;
import net.sf.saxon.trans.XPathException; import net.sf.saxon.trans.XPathException;
@ -63,20 +72,45 @@ import net.sf.saxon.trans.XPathException;
@Slf4j @Slf4j
public class ObjectFactory { public class ObjectFactory {
private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver {
public static final String MESSAGE = "Configuration error. Resolving ist not allowed";
@Override
public OutputURIResolver newInstance() {
return this;
}
@Override
public Result resolve(final String href, final String base) throws TransformerException {
throw new IllegalStateException(MESSAGE);
}
@Override
public void close(final Result result) throws TransformerException {
throw new IllegalStateException(MESSAGE);
}
@Override
public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException {
throw new IllegalStateException(MESSAGE);
}
@Override
public ResourceCollection findCollection(final XPathContext context, final String collectionURI) throws XPathException {
throw new IllegalStateException(MESSAGE);
}
}
private static final String ORACLE_XERCES_CLASS = "com.sun.org.apache.xerces.internal.impl.Constants"; private static final String ORACLE_XERCES_CLASS = "com.sun.org.apache.xerces.internal.impl.Constants";
private static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; private static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
private static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; private static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
private static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; private static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing";
private static Processor processor; private static Processor processor;
static { static {
try { try {
Class.forName(ORACLE_XERCES_CLASS); Class.forName(ORACLE_XERCES_CLASS);
} catch (ClassNotFoundException e) { } catch (final ClassNotFoundException e) {
log.warn("No oracle JDK version of XERCES found. Configured security features may not have any effect."); log.warn("No oracle JDK version of XERCES found. Configured security features may not have any effect.");
log.warn("Please take care of XML security while checking your xml contents"); log.warn("Please take care of XML security while checking your xml contents");
} }
@ -86,8 +120,8 @@ public class ObjectFactory {
// hide, it's a factory // hide, it's a factory
} }
private static DocumentBuilderFactory createDocumentBuilderFactory(boolean validating) { private static DocumentBuilderFactory createDocumentBuilderFactory(final boolean validating) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try { try {
dbf.setValidating(validating); dbf.setValidating(validating);
dbf.setNamespaceAware(true); dbf.setNamespaceAware(true);
@ -105,7 +139,7 @@ public class ObjectFactory {
dbf.setXIncludeAware(false); dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false); dbf.setExpandEntityReferences(false);
return dbf; return dbf;
} catch (ParserConfigurationException e) { } catch (final ParserConfigurationException e) {
throw new IllegalStateException("Can not create DocumentBuilderFactory due to underlying configuration error", e); throw new IllegalStateException("Can not create DocumentBuilderFactory due to underlying configuration error", e);
} }
@ -117,7 +151,7 @@ public class ObjectFactory {
* @param prettyPrint pretty-printing der Ausgabe * @param prettyPrint pretty-printing der Ausgabe
* @return einen vorkonfigurierten Transformer * @return einen vorkonfigurierten Transformer
*/ */
public static Transformer createTransformer(boolean prettyPrint) { public static Transformer createTransformer(final boolean prettyPrint) {
Transformer transformer = null; Transformer transformer = null;
try { try {
transformer = TransformerFactory.newInstance().newTransformer(); transformer = TransformerFactory.newInstance().newTransformer();
@ -129,7 +163,7 @@ public class ObjectFactory {
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
} }
return transformer; return transformer;
} catch (TransformerConfigurationException e) { } catch (final TransformerConfigurationException e) {
throw new IllegalStateException("Can not create Transformer due to underlying configuration error", e); throw new IllegalStateException("Can not create Transformer due to underlying configuration error", e);
} }
} }
@ -141,27 +175,27 @@ public class ObjectFactory {
*/ */
public static XMLGregorianCalendar createTimestamp() { public static XMLGregorianCalendar createTimestamp() {
try { try {
GregorianCalendar cal = new GregorianCalendar(); final GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date()); cal.setTime(new Date());
return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal); return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
} catch (DatatypeConfigurationException e) { } catch (final DatatypeConfigurationException e) {
throw new IllegalStateException("Can not create timestamp", e); throw new IllegalStateException("Can not create timestamp", e);
} }
} }
public static DocumentBuilder createDocumentBuilder(boolean validating) { public static DocumentBuilder createDocumentBuilder(final boolean validating) {
try { try {
return createDocumentBuilderFactory(validating).newDocumentBuilder(); return createDocumentBuilderFactory(validating).newDocumentBuilder();
} catch (ParserConfigurationException e) { } catch (final ParserConfigurationException e) {
throw new IllegalStateException("Can not create DocumentFactory due to underlying configuration error", e); throw new IllegalStateException("Can not create DocumentFactory due to underlying configuration error", e);
} }
} }
private static String encode(String input) { private static String encode(final String input) {
try { try {
return URLEncoder.encode(input, StandardCharsets.UTF_8.name()); return URLEncoder.encode(input, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) { } catch (final UnsupportedEncodingException e) {
throw new IllegalStateException("Error encoding property while initializing saxon", e); throw new IllegalStateException("Error encoding property while initializing saxon", e);
} }
} }
@ -170,7 +204,7 @@ public class ObjectFactory {
if (processor == null) { if (processor == null) {
processor = new Processor(false); processor = new Processor(false);
//verhindere global im Prinzip alle resolving strategien //verhindere global im Prinzip alle resolving strategien
SecureUriResolver resolver = new SecureUriResolver(); final SecureUriResolver resolver = new SecureUriResolver();
processor.getUnderlyingConfiguration().setCollectionFinder(resolver); processor.getUnderlyingConfiguration().setCollectionFinder(resolver);
processor.getUnderlyingConfiguration().setOutputURIResolver(resolver); processor.getUnderlyingConfiguration().setOutputURIResolver(resolver);
//hier fehlt eigentlich noch der UriResolver für unparsed text, wird erst ab Saxon 9.8 unterstützt //hier fehlt eigentlich noch der UriResolver für unparsed text, wird erst ab Saxon 9.8 unterstützt
@ -183,7 +217,7 @@ public class ObjectFactory {
// Konfiguration des zu verwendenden Parsers, wenn Saxon selbst einen erzeugen muss, bspw. beim XSL parsen // Konfiguration des zu verwendenden Parsers, wenn Saxon selbst einen erzeugen muss, bspw. beim XSL parsen
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(FEATURE_SECURE_PROCESSING), true); processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(FEATURE_SECURE_PROCESSING), true);
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(DISSALLOW_DOCTYPE_DECL_FEATURE), false); processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(DISSALLOW_DOCTYPE_DECL_FEATURE), true);
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(LOAD_EXTERNAL_DTD_FEATURE), false); processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(LOAD_EXTERNAL_DTD_FEATURE), false);
} }
return processor; return processor;
@ -195,17 +229,17 @@ public class ObjectFactory {
* @param schema das Schema mit dem validiert werden soll * @param schema das Schema mit dem validiert werden soll
* @return einen vorkonfigurierten Validator * @return einen vorkonfigurierten Validator
*/ */
public static Validator createValidator(Schema schema) { public static Validator createValidator(final Schema schema) {
final Validator validator = schema.newValidator(); final Validator validator = schema.newValidator();
try { try {
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
} catch (SAXNotRecognizedException | SAXNotSupportedException e) { } catch (final SAXNotRecognizedException | SAXNotSupportedException e) {
log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used."); log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
log.debug(e.getMessage(), e); log.debug(e.getMessage(), e);
} }
try { try {
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (SAXNotRecognizedException | SAXNotSupportedException e) { } catch (final SAXNotRecognizedException | SAXNotSupportedException e) {
log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used."); log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
log.debug(e.getMessage(), e); log.debug(e.getMessage(), e);
@ -214,43 +248,13 @@ public class ObjectFactory {
} }
public static SchemaFactory createSchemaFactory() { public static SchemaFactory createSchemaFactory() {
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try { try {
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file"); sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file");
} catch (SAXException e) { } catch (final SAXException e) {
log.warn("Can not disable external DTD access, maybe an unsupported JAXP implementation is used", e); log.warn("Can not disable external DTD access, maybe an unsupported JAXP implementation is used", e);
} }
return sf; return sf;
} }
private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver {
public static final String MESSAGE = "Configuration error. Resolving ist not allowed";
@Override
public OutputURIResolver newInstance() {
return this;
}
@Override
public Result resolve(String href, String base) throws TransformerException {
throw new IllegalStateException(MESSAGE);
}
@Override
public void close(Result result) throws TransformerException {
throw new IllegalStateException(MESSAGE);
}
@Override
public Reader resolve(URI absoluteURI, String encoding, Configuration config) throws XPathException {
throw new IllegalStateException(MESSAGE);
}
@Override
public ResourceCollection findCollection(XPathContext context, String collectionURI) throws XPathException {
throw new IllegalStateException(MESSAGE);
}
}
} }

View file

@ -19,10 +19,12 @@
package de.kosit.validationtool.impl; package de.kosit.validationtool.impl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.stream.Collectors;
import javax.xml.transform.dom.DOMSource; import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
@ -41,6 +43,14 @@ import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XsltCompiler; import net.sf.saxon.s9api.XsltCompiler;
import net.sf.saxon.s9api.XsltExecutable; import net.sf.saxon.s9api.XsltExecutable;
import net.sf.saxon.s9api.XsltTransformer; import net.sf.saxon.s9api.XsltTransformer;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.DocumentParseAction;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import net.sf.saxon.s9api.XdmNode;
/** /**
* Testet verschiedene Saxon Security Einstellungen. * Testet verschiedene Saxon Security Einstellungen.
@ -76,8 +86,18 @@ public class SaxonSecurityTest {
} }
} catch (final SaxonApiException | RuntimeException e) { } catch (final SaxonApiException | RuntimeException e) {
log.info("Expected exception detected", e.getMessage()); log.info("Expected exception detected {}", e.getMessage(), e);
} }
} }
} }
@Test
public void testXxe() {
final URL resource = SaxonSecurityTest.class.getResource("/evil/xxe.xml");
final Result<XdmNode, XMLSyntaxError> result = DocumentParseAction.parseDocument(InputFactory.read(resource));
assertThat(result.isValid()).isFalse();
assertThat(result.getObject()).isNull();
assertThat(result.getErrors().stream().map(XMLSyntaxError::getMessage).collect(Collectors.joining()))
.contains("http://apache.org/xml/features/disallow-doctype-dec");
}
} }

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "ref.txt" >]><foo>&xxe;</foo>