diff --git a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java b/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java index 5245e70..eace869 100644 --- a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java +++ b/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java @@ -34,7 +34,12 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; 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.SchemaFactory; import javax.xml.validation.Validator; @@ -47,7 +52,11 @@ import lombok.extern.slf4j.Slf4j; import net.sf.saxon.Configuration; 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.trans.XPathException; @@ -63,20 +72,45 @@ import net.sf.saxon.trans.XPathException; @Slf4j 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 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 FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; - private static Processor processor; static { try { 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("Please take care of XML security while checking your xml contents"); } @@ -86,8 +120,8 @@ public class ObjectFactory { // hide, it's a factory } - private static DocumentBuilderFactory createDocumentBuilderFactory(boolean validating) { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + private static DocumentBuilderFactory createDocumentBuilderFactory(final boolean validating) { + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { dbf.setValidating(validating); dbf.setNamespaceAware(true); @@ -105,7 +139,7 @@ public class ObjectFactory { dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); return dbf; - } catch (ParserConfigurationException e) { + } catch (final ParserConfigurationException e) { throw new IllegalStateException("Can not create DocumentBuilderFactory due to underlying configuration error", e); } @@ -113,11 +147,11 @@ public class ObjectFactory { /** * Transformer für die Ausgabe. Nutzt nicht Saxon! - * + * * @param prettyPrint pretty-printing der Ausgabe * @return einen vorkonfigurierten Transformer */ - public static Transformer createTransformer(boolean prettyPrint) { + public static Transformer createTransformer(final boolean prettyPrint) { Transformer transformer = null; try { transformer = TransformerFactory.newInstance().newTransformer(); @@ -129,7 +163,7 @@ public class ObjectFactory { transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); } return transformer; - } catch (TransformerConfigurationException e) { + } catch (final TransformerConfigurationException 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() { try { - GregorianCalendar cal = new GregorianCalendar(); + final GregorianCalendar cal = new GregorianCalendar(); cal.setTime(new Date()); return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal); - } catch (DatatypeConfigurationException e) { + } catch (final DatatypeConfigurationException e) { throw new IllegalStateException("Can not create timestamp", e); } } - public static DocumentBuilder createDocumentBuilder(boolean validating) { + public static DocumentBuilder createDocumentBuilder(final boolean validating) { try { return createDocumentBuilderFactory(validating).newDocumentBuilder(); - } catch (ParserConfigurationException e) { + } catch (final ParserConfigurationException 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 { 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); } } @@ -170,7 +204,7 @@ public class ObjectFactory { if (processor == null) { processor = new Processor(false); //verhindere global im Prinzip alle resolving strategien - SecureUriResolver resolver = new SecureUriResolver(); + final SecureUriResolver resolver = new SecureUriResolver(); processor.getUnderlyingConfiguration().setCollectionFinder(resolver); processor.getUnderlyingConfiguration().setOutputURIResolver(resolver); //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 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); } return processor; @@ -195,17 +229,17 @@ public class ObjectFactory { * @param schema das Schema mit dem validiert werden soll * @return einen vorkonfigurierten Validator */ - public static Validator createValidator(Schema schema) { + public static Validator createValidator(final Schema schema) { final Validator validator = schema.newValidator(); try { 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.debug(e.getMessage(), e); } try { 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.debug(e.getMessage(), e); @@ -214,43 +248,13 @@ public class ObjectFactory { } 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 { sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 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); } 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); - } - } } diff --git a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java b/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java index ea3ccec..0729446 100644 --- a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java +++ b/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java @@ -19,10 +19,12 @@ package de.kosit.validationtool.impl; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import java.io.IOException; import java.net.URL; +import java.util.stream.Collectors; import javax.xml.transform.dom.DOMSource; 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.XsltExecutable; 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. @@ -76,8 +86,18 @@ public class SaxonSecurityTest { } } 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 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"); + } } diff --git a/src/test/resources/evil/xxe.xml b/src/test/resources/evil/xxe.xml new file mode 100644 index 0000000..aba8a35 --- /dev/null +++ b/src/test/resources/evil/xxe.xml @@ -0,0 +1,4 @@ + + + ]>&xxe;