(fix) correctly disable xxe with saxon api

This commit is contained in:
Andreas Penski (init) 2020-02-17 16:49:26 +01:00
parent b6141bce23
commit ed3f37eb3f
3 changed files with 93 additions and 62 deletions

View file

@ -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);
}
}
}

View file

@ -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;
@ -33,7 +35,18 @@ import org.w3c.dom.Document;
import lombok.extern.slf4j.Slf4j;
import net.sf.saxon.s9api.*;
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.DOMDestination;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XsltCompiler;
import net.sf.saxon.s9api.XsltExecutable;
import net.sf.saxon.s9api.XsltTransformer;
/**
* Testet verschiedene Saxon Security Einstellungen.
@ -45,10 +58,10 @@ public class SaxonSecurityTest {
@Test
public void testEvilStylesheets() throws IOException {
Processor p = ObjectFactory.createProcessor();
final Processor p = ObjectFactory.createProcessor();
for (int i = 1; i <= 5; i++) {
try {
URL resource = SaxonSecurityTest.class.getResource(String.format("/evil/evil%s.xsl", i));
final URL resource = SaxonSecurityTest.class.getResource(String.format("/evil/evil%s.xsl", i));
final XsltCompiler compiler = p.newXsltCompiler();
final RelativeUriResolver resolver = new RelativeUriResolver(Helper.REPOSITORY);
compiler.setURIResolver(resolver);
@ -56,7 +69,7 @@ public class SaxonSecurityTest {
final XsltTransformer transformer = exetuable.load();
final Document document = ObjectFactory.createDocumentBuilder(false).newDocument();
document.createElement("root");
Document result = ObjectFactory.createDocumentBuilder(false).newDocument();
final Document result = ObjectFactory.createDocumentBuilder(false).newDocument();
transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver);
transformer.setURIResolver(resolver);
transformer.setSource(new DOMSource(document));
@ -68,9 +81,19 @@ public class SaxonSecurityTest {
fail(String.format("Saxon configuration should prevent expansion within %s", resource));
}
} catch (SaxonApiException | RuntimeException e) {
log.info("Expected exception detected", e.getMessage());
} catch (final SaxonApiException | RuntimeException e) {
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>