Skip to content
Permalink
Browse files Browse the repository at this point in the history
TMDM-15182 Improper Restriction of XML External Entity Reference (#1420)
* TMDM-15182 Improper Restriction of XML External Entity Reference
  • Loading branch information
hw-zhu committed Dec 14, 2021
1 parent 1cf8126 commit 31d442b
Show file tree
Hide file tree
Showing 33 changed files with 256 additions and 702 deletions.
Expand Up @@ -9,14 +9,12 @@
*/
package com.amalto.commons.core.utils;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
Expand All @@ -25,17 +23,15 @@
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.DocumentSource;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.org.apache.xpath.internal.XPathAPI;
import com.sun.org.apache.xpath.internal.objects.XObject;
Expand All @@ -49,177 +45,29 @@ public final class XMLUtils {

private static final Logger logger = LogManager.getLogger(XMLUtils.class);

private static final TransformerFactory transformerFactory = TransformerFactory.newInstance();
private static final TransformerFactory transformerFactory;

private static final TransformerFactory saxonTransformerFactory = new net.sf.saxon.TransformerFactoryImpl();

/**
* Parsed an XML String into a {@link Document} without schema vaildation
* @param xmlString
* @return the Document
* @throws ParserConfigurationException,IOException, SAXException
*/
public static Document parse(String xmlString) throws ParserConfigurationException,IOException, SAXException{
return parse(xmlString,null);
}


/**
* Parses an XML String into a Document<br>
* The thrown Exception will contain validation errors when a schema is provided.
* @param xmlString
* @param schema - the schema XSD
* @return The org.w3c.dom.Document
* @throws ParserConfigurationException,IOException, SAXException
*/
public static Document parse(String xmlString, String schema) throws ParserConfigurationException,IOException, SAXException{

//parse
Document d=null;
SAXErrorHandler seh = new SAXErrorHandler();

//initialize the sax parser which uses Xerces
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setExpandEntityReferences(false);
factory.setXIncludeAware(false);
//Schema validation based on schemaURL
factory.setNamespaceAware(true);
factory.setValidating((schema!=null));
factory.setAttribute(
"http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema");
if (schema != null) {
factory.setAttribute(
"http://java.sun.com/xml/jaxp/properties/schemaSource",
new InputSource(new StringReader(schema))
);
}
DocumentBuilder builder;
builder = factory.newDocumentBuilder();
builder.setErrorHandler(seh);
d = builder.parse(new InputSource(new StringReader(xmlString)));


//check if dcument parsed correctly against the schema
if (schema != null) {
String errors = seh.getErrors();
if (!errors.equals("")) {
String err = "Document did not parse against schema: \n" + errors+"\n"
+xmlString.substring(0, Math.min(100, xmlString.length()));
LogManager.getLogger(XMLUtils.class).error(err);
throw new SAXException(err);
}
}
return d;
}


/**
* Return the String values of the Text Nodes below an xPath
* @param contextNode
* @param xPath
* @return a String Array of the text node values
* @throws TransformerException
*/
public static String[] getTextNodes(Node contextNode, String xPath) throws TransformerException{
return getTextNodes(contextNode,xPath,contextNode);
}


/**
* Return the String values of the Text Nodes below an xPath<br>
* Where the xPath contains namespace, a context node holding the namespaces definitions can be provided
* @param contextNode
* @param xPath
* @param namespaceNode
* @return a String Array of the text node values
* @throws TransformerException
*/
public static String[] getTextNodes(Node contextNode, String xPath,Node namespaceNode) throws TransformerException{
String[] results=null;;

//test for hard-coded values
if (xPath.startsWith("\"") && xPath.endsWith("\""))
return new String[] { xPath.substring(1, xPath.length()-1)};

//test for incomplete path (elements missing /text())
if (!xPath.matches(".*@[^/\\]]+")) //attribute
if (! xPath.endsWith(")")) //function
xPath+="/text()";

static {
// the open jdk implementation allows the disabling of the feature used for XXE
System.setProperty("javax.xml.transform.TransformerFactory",
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
transformerFactory = TransformerFactory.newInstance();
try {
XObject xo = XPathAPI.eval(contextNode, xPath,namespaceNode);
if (xo.getType() == XObject.CLASS_NODESET) {
NodeList l = xo.nodelist();
int len = l.getLength();
results = new String[len];
for (int i = 0; i < len; i++) {
Node n = l.item(i);
results[i] = n.getNodeValue();
}
} else {
results = new String[]{xo.toString()};
}
} catch (Exception e) {
String err = "Unable to get the text node(s) of "+xPath
+": " + e.getClass().getName() + ": "
+ e.getLocalizedMessage();
LogManager.getLogger(XMLUtils.class).error(err,e);
throw new TransformerException(err);
}
return results;

}


/**
* The value of the first text node at the xPath<br>
* @see #getTextNodes(Node, String, Node)
* @param contextNode
* @param xPath
* @param namespaceNode
* @return the String value
* @throws TransformerException
*/
public static String getFirstTextNode(Node contextNode, String xPath,Node namespaceNode) throws TransformerException{
String[] res = getTextNodes(contextNode,xPath,namespaceNode);
if (res.length == 0)
return null;
return res[0];
}

/**
* The value of the first text node at the xPath<br>
* @see #getTextNodes(Node, String)
* @param contextNode
* @param xPath
* @return teh String value
* @throws TransformerException
*/
public static String getFirstTextNode(Node contextNode, String xPath) throws TransformerException{
return getFirstTextNode(contextNode,xPath,contextNode);
}

transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

/**
* The value of the first text node at the xPath which is not null<br>
* @see #getTextNodes(Node, String)
* @param contextNode
* @param xPath
* @return teh String value
* @throws TransformerException
*/
public static String getFirstTextNodeNotNull(Node contextNode, String xPath) throws TransformerException {
String val= getFirstTextNode(contextNode,xPath,contextNode);
return val == null ? "" : val;
saxonTransformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
saxonTransformerFactory.setAttribute("http://saxon.sf.net/feature/version-warning", Boolean.FALSE);
saxonTransformerFactory.setAttribute("http://saxon.sf.net/feature/allow-external-functions", Boolean.FALSE);
saxonTransformerFactory.setAttribute("http://saxon.sf.net/feature/trace-external-functions", Boolean.FALSE);
} catch (TransformerConfigurationException e) {
// Just catch this, as Xalan doesn't support the above
}
}




/**
* Returns a namespaced root element of a document
* Useful to create a namespace holder element
Expand Down Expand Up @@ -280,7 +128,6 @@ public static NodeList getNodeList(Node contextNode, String xPath, String namesp
}
}


/**
* Returns the first Element of the Node List at xPath
* @param n
Expand All @@ -294,58 +141,6 @@ public static Element getFirstElement(Node n, String xPath) throws TransformerEx
return (Element)nl.item(0);
}


/**
* Validates the element against the provided XSD schema
* @param element
* @param schema
* @return
* @throws SAXException
* @throws ParserConfigurationException
* @throws IOException
* @throws TransformerException
*/
public static Document validate(Element element, String schema) throws SAXException, ParserConfigurationException,
IOException, TransformerException {

LogManager.getLogger(XMLUtils.class).trace("validate() " + element.getLocalName());

// parse
Document d = null;
SAXErrorHandler seh = new SAXErrorHandler();

// initialize the sax parser which uses Xerces
System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Schema validation based on schemaURL
factory.setNamespaceAware(true);
factory.setExpandEntityReferences(false);
factory.setValidating((schema != null));
factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
if (schema != null) {
factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource",
new InputSource(new StringReader(schema)));
}
DocumentBuilder builder;
builder = factory.newDocumentBuilder();
builder.setErrorHandler(seh);
d = builder.parse(new InputSource(new StringReader(nodeToString(element))));

// check if dcument parsed correctly against the schema
if (schema != null) {
String errors = seh.getErrors();
if (!errors.equals("")) {
String xmlString = nodeToString(element);
String err = "The item " + element.getLocalName() + " did not validate against the model: \n" + errors + "\n"
+ xmlString; // .substring(0, Math.min(100, xmlString.length()));
throw new SAXException(err);
}
}
return d;
}



public static String[] getAttributeNodeValue(Node contextNode, String xPath, Node namespaceNode) throws TransformerException{
String[] results;

Expand Down Expand Up @@ -379,12 +174,6 @@ public static String[] getAttributeNodeValue(Node contextNode, String xPath, Nod
}

public static Transformer generateTransformer() throws TransformerConfigurationException {
try {
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
} catch (Exception e) {
e.printStackTrace();
}
return transformerFactory.newTransformer();
}

Expand Down
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;
import java.util.UUID;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
Expand All @@ -47,7 +48,6 @@
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import com.amalto.commons.core.utils.XMLUtils;
import com.amalto.core.storage.StorageType;
import com.amalto.core.storage.datasource.RDBMSDataSource;
import com.amalto.core.storage.datasource.RDBMSDataSourceBuilder;
Expand Down Expand Up @@ -90,6 +90,9 @@ public InputStream generateHibernateMapping() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setExpandEntityReferences(false);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
documentBuilder.setEntityResolver(HibernateStorage.ENTITY_RESOLVER);
Document document = documentBuilder.parse(this.getClass().getResourceAsStream(HIBERNATE_MAPPING_TEMPLATE));
Expand Down Expand Up @@ -170,7 +173,9 @@ public Document generateHibernateConfiguration(RDBMSDataSource rdbmsDataSource)
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setExpandEntityReferences(false);

factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
documentBuilder.setEntityResolver(HibernateStorage.ENTITY_RESOLVER);
Document document = documentBuilder.parse(DefaultStorageClassLoader.class.getResourceAsStream(HIBERNATE_CONFIG_TEMPLATE));
Expand Down Expand Up @@ -291,7 +296,7 @@ private static void setPropertyValue(Document document, String propertyName, Str

protected InputStream toInputStream(Document document) throws Exception {
StringWriter buffer = new StringWriter();
Transformer transformer = XMLUtils.generateTransformer();
Transformer transformer = MDMXMLUtils.generateTransformer();
DocumentType doctype = document.getDoctype();
if (doctype != null) {
if (doctype.getPublicId() != null) {
Expand Down
Expand Up @@ -32,10 +32,9 @@
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import com.amalto.core.delegator.MockILocalUser;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
Expand All @@ -49,9 +48,9 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.amalto.commons.core.utils.XMLUtils;
import com.amalto.core.delegator.BaseSecurityCheck;
import com.amalto.core.delegator.BeanDelegatorContainer;
import com.amalto.core.delegator.MockILocalUser;
import com.amalto.core.history.DeleteType;
import com.amalto.core.history.MutableDocument;
import com.amalto.core.objects.UpdateReportPOJO;
Expand Down Expand Up @@ -1721,7 +1720,7 @@ public void testUpdateConf() throws Exception {
+ "<key>[HEAD].CoreTestsContainer.auto_increment.auto_increment</key>" + "<value>1</value>" + "</entry>"
+ "<entry>" + "<key>[HEAD].Product.ProductFamily.Id</key>" + "<value>30</value>" + "</entry>" + "<entry>"
+ "<key>[HEAD].CoreTestsContainer.auto_increment1.auto_increment1</key>" + "<value>1</value>" + "</entry>"
+ "</AutoIncrement>", XMLUtils.nodeToString(committedElement, true, false).replaceAll("\r\n", "\n"));
+ "</AutoIncrement>", MDMXMLUtils.nodeToString(committedElement, true, false).replaceAll("\r\n", "\n"));
assertTrue(source.hasCalledInitAutoIncrement);

}
Expand Down
Expand Up @@ -24,8 +24,8 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.amalto.commons.core.utils.SAXErrorHandler;
import com.amalto.core.util.CVCException;
import com.amalto.core.util.SAXErrorHandler;
import com.amalto.core.util.Util;

public class IValidation {
Expand Down

0 comments on commit 31d442b

Please sign in to comment.