mirror of
https://github.com/itplr-kosit/validator.git
synced 2026-05-25 16:55:39 +00:00
(fix) correctly disable xxe with saxon api
This commit is contained in:
parent
b6141bce23
commit
ed3f37eb3f
3 changed files with 93 additions and 62 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +151,7 @@ public class ObjectFactory {
|
|||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
src/test/resources/evil/xxe.xml
Normal file
4
src/test/resources/evil/xxe.xml
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue