blob: ee95819d8256568a5c022d36764bcc3e53406d79 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018, 2019 Ericsson
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.common.core.xml;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.common.core.Activator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
/**
* XML Utilities. Useful to avoid copy-pasting secure code generation. Utils
* here should be OWASP compliant.
*
* @author Matthew Khouzam
* @since 3.2
*/
public final class XmlUtils {
private static final String EMPTY = ""; //$NON-NLS-1$
private static final String DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; //$NON-NLS-1$
private static final String NONVALIDATING_LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; //$NON-NLS-1$
private static final String USE_EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities"; //$NON-NLS-1$
private static final String ACCESS_EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities"; //$NON-NLS-1$
private XmlUtils() {
// Do nothing
}
/**
* <p>
* Create a new <code>Transformer</code> that performs a copy of the
* <code>Source</code> to the <code>Result</code>. i.e. the "<em>identity
* transform</em>".
* </p>
* <p>
* This is thread safe.
* </p>
* <p>
* Use {@link XMLConstants#FEATURE_SECURE_PROCESSING} to ensure that the
* transformer is secure.
* </p>
*
* @return A Transformer object that may be used to perform a transformation
* in a single thread, never null.
*
* @throws TransformerConfigurationException
* When it is not possible to create a <code>Transformer</code>
* instance.
*/
public static Transformer newSecureTransformer() throws TransformerConfigurationException {
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
return factory.newTransformer();
}
/**
* Create a document builder factory that is safe according to the OWASP
* injection prevention cheat sheet.
*
* @return the documentBuilderFactory
* @since 4.1
*/
public static DocumentBuilderFactory newSafeDocumentBuilderFactory() {
String feature = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
// This one is from Sonar (squid:S2755)
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// This is the PRIMARY defense. If DTDs (doctypes) are disallowed,
// almost all
// XML entity attacks are prevented
// Xerces 2 only -
// http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
feature = DISALLOW_DOCTYPE_DECL;
dbf.setFeature(feature, true);
// If you can't completely disable DTDs, then at least do the
// following:
// Xerces 1 -
// http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 -
// http://xerces.apache.org/xerces2-j/features.html#external-general-entities
// JDK7+ - http://xml.org/sax/features/external-general-entities
feature = ACCESS_EXTERNAL_GENERAL_ENTITIES;
dbf.setFeature(feature, false);
// Xerces 1 -
// http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
// Xerces 2 -
// http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
// JDK7+ - http://xml.org/sax/features/external-parameter-entities
feature = USE_EXTERNAL_PARAMETER_ENTITIES;
dbf.setFeature(feature, false);
// Disable external DTDs as well
feature = NONVALIDATING_LOAD_EXTERNAL_DTD;
dbf.setFeature(feature, false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema,
// DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
// And, per Timothy Morgan:
// "If for some reason support for inline DOCTYPEs are a requirement, then ensure
// the entity settings are disabled (as shown above) and beware that SSRF attacks
// (http://cwe.mitre.org/data/definitions/918.html)
// and denial of service attacks (such as billion laughs or decompression bombs via
// "jar:") are a risk."
} catch (ParserConfigurationException e) {
// This should catch a failed setFeature feature
Activator.instance().logInfo("ParserConfigurationException was thrown. The feature '" + feature //$NON-NLS-1$
+ "' is probably not supported by your XML processor.", e); //$NON-NLS-1$
}
return dbf;
}
/**
* Create a new safe {@link XMLStreamReader} from an {@link InputStream}
*
* @param inputStream
* the {@link InputStream} to read
* @return an XML stream reader
* @throws XMLStreamException
* if the stream could not be read or is badly formatted
* @since 4.2
*/
public static @Nullable XMLStreamReader newSafeXmlStreamReader(InputStream inputStream) throws XMLStreamException {
XMLInputFactory factory = XMLInputFactory.newInstance();
/*
* Disable XML External entity (XXE) parsing
*/
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
return factory.createXMLStreamReader(inputStream);
}
/**
* Get a safe {@link SchemaFactory}
*
* @return a safe {@link SchemaFactory}
* @throws SAXException
* parse exception with
* {@link XMLConstants#W3C_XML_SCHEMA_NS_URI}
* @since 4.2
*/
public static SchemaFactory newSafeSchemaFactory() throws SAXException {
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, EMPTY);
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, EMPTY);
return factory;
}
/**
* Validate a source, throws an exception if invalid.
*
* @param schema
* the schema to verify against. Use
* {@link #newSafeSchemaFactory()} for a safe schema
* @param source
* the source to verify
* @throws SAXException
* when the underlying XMLReader cannot set the security
* properties -or- when the source fails validation
* @throws IOException
* the source failed validation due to file reasons, e.g.
* permissions
* @since 4.2
*/
public static void safeValidate(Schema schema, Source source) throws SAXException, IOException {
Validator validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, EMPTY);
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, EMPTY);
validator.validate(source);
}
/**
* Create a new secure {@link SAXParserFactory}
*
* @return a new secure {@link SAXParserFactory}
* @throws ParserConfigurationException
* The parser cannot be created with the default configuration
* @throws SAXNotRecognizedException
* When the underlying XMLReader does not recognize the property
* name.
* @throws SAXNotSupportedException
* When the underlying XMLReader does recognize the property
* name but does not support it
* @since 4.2
*/
public static SAXParserFactory newSafeSaxParserFactory() throws SAXNotRecognizedException, SAXNotSupportedException, ParserConfigurationException {
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
parserFactory.setFeature(ACCESS_EXTERNAL_GENERAL_ENTITIES, false);
parserFactory.setFeature(USE_EXTERNAL_PARAMETER_ENTITIES, false);
parserFactory.setFeature(NONVALIDATING_LOAD_EXTERNAL_DTD, false);
parserFactory.setFeature(DISALLOW_DOCTYPE_DECL, true);
parserFactory.setNamespaceAware(true);
return parserFactory;
}
}