Merge pull request #1082 from langfr/feature/xmlTools

Centralize and secure DocumentBuilder creation.
This commit is contained in:
Jochen Staerk
2026-04-07 15:46:11 +02:00
committed by GitHub
9 changed files with 91 additions and 111 deletions

View File

@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.net.URL;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
@@ -11,11 +12,19 @@ import org.apache.commons.io.IOUtils;
import org.dom4j.io.XMLWriter; import org.dom4j.io.XMLWriter;
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat; import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
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;
public class XMLTools extends XMLWriter { public class XMLTools extends XMLWriter {
@Override @Override
@@ -28,29 +37,85 @@ public class XMLTools extends XMLWriter {
return super.escapeElementEntities(s); return super.escapeElementEntities(s);
} }
public static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { public static DocumentBuilder getDocumentBuilder(boolean namespaceAware) throws ParserConfigurationException {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT //REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf //https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); try {
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); } catch (ParserConfigurationException e) {
// ignore
}
try {
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
} catch (IllegalArgumentException e) {
// ignore
}
try {
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (IllegalArgumentException e) {
// ignore
}
//OWASP //OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); try {
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); } catch (ParserConfigurationException e) {
// ignore
}
try {
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
} catch (ParserConfigurationException e) {
// ignore
}
try {
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
} catch (ParserConfigurationException e) {
// ignore
}
try {
// Disable external DTDs as well // Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (ParserConfigurationException e) {
// ignore
}
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false); dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false); dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true); dbf.setNamespaceAware(namespaceAware);
return dbf.newDocumentBuilder(); return dbf.newDocumentBuilder();
} }
public static Validator getValidator(URL schemaFile) throws SAXException
{
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
} catch (SAXNotSupportedException | SAXNotRecognizedException e) {
// ignore
}
Schema schema = schemaFactory.newSchema(schemaFile);
Validator validator = schema.newValidator();
try {
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (SAXNotSupportedException | SAXNotRecognizedException e) {
// ignore
}
return validator;
}
public static TransformerFactory getTransformerFactory()
{
TransformerFactory factory = new net.sf.saxon.TransformerFactoryImpl();
try {
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (TransformerConfigurationException e) {
// ignore
}
return factory;
}
public static String nDigitFormat(BigDecimal value, int scale) { public static String nDigitFormat(BigDecimal value, int scale) {
/* /*

View File

@@ -15,6 +15,7 @@ import org.apache.fop.configuration.ConfigurationException;
import org.apache.fop.configuration.DefaultConfigurationBuilder; import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.xmlgraphics.util.MimeConstants; import org.apache.xmlgraphics.util.MimeConstants;
import org.mustangproject.ClasspathResolverURIAdapter; import org.mustangproject.ClasspathResolverURIAdapter;
import org.mustangproject.XMLTools;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -42,8 +43,7 @@ public class ValidationLogVisualizer {
public ValidationLogVisualizer() { public ValidationLogVisualizer() {
mFactory = new net.sf.saxon.TransformerFactoryImpl(); mFactory = XMLTools.getTransformerFactory();
// fact = TransformerFactory.newInstance();
mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver()); mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver());
} }

View File

@@ -16,6 +16,8 @@ import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import org.mustangproject.XMLTools;
/*** /***
* Uses a XSLT transformation to upgrade * Uses a XSLT transformation to upgrade
@@ -31,8 +33,7 @@ public class XMLUpgrader {
private Templates mXsltTemplate = null; private Templates mXsltTemplate = null;
public XMLUpgrader() { public XMLUpgrader() {
mFactory = new net.sf.saxon.TransformerFactoryImpl(); mFactory = XMLTools.getTransformerFactory();
//fact = TransformerFactory.newInstance();
mFactory.setURIResolver(new ClasspathResourceURIResolver()); mFactory.setURIResolver(new ClasspathResourceURIResolver());
} }

View File

@@ -18,9 +18,7 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathConstants;
@@ -341,7 +339,7 @@ public class ZUGFeRDInvoiceImporter {
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is)); /// is.skip(guessBOMSize(is));
try { try {
DocumentBuilder builder = XMLTools.getDocumentBuilder(); DocumentBuilder builder = XMLTools.getDocumentBuilder(true);
document = builder.parse(is); document = builder.parse(is);
} catch (Exception e) { } catch (Exception e) {
@@ -356,7 +354,7 @@ public class ZUGFeRDInvoiceImporter {
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is)); /// is.skip(guessBOMSize(is));
DocumentBuilder builder = XMLTools.getDocumentBuilder(); DocumentBuilder builder = XMLTools.getDocumentBuilder(true);
if (canParse()) { if (canParse()) {
document = builder.parse(is); document = builder.parse(is);
if (parseAutomatically) { if (parseAutomatically) {

View File

@@ -32,6 +32,7 @@ import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.xmlgraphics.util.MimeConstants; import org.apache.xmlgraphics.util.MimeConstants;
import org.mustangproject.ClasspathResolverURIAdapter; import org.mustangproject.ClasspathResolverURIAdapter;
import org.mustangproject.EStandard; import org.mustangproject.EStandard;
import org.mustangproject.XMLTools;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.w3c.dom.Document; import org.w3c.dom.Document;
@@ -39,7 +40,6 @@ import org.w3c.dom.Element;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*; import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;
@@ -47,8 +47,6 @@ import javax.xml.transform.stream.StreamSource;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -85,8 +83,7 @@ public class ZUGFeRDVisualizer {
private Templates mXsltZF1HTMLTemplate = null; private Templates mXsltZF1HTMLTemplate = null;
public ZUGFeRDVisualizer() { public ZUGFeRDVisualizer() {
mFactory = new net.sf.saxon.TransformerFactoryImpl(); mFactory = XMLTools.getTransformerFactory();
// fact = TransformerFactory.newInstance();
mFactory.setURIResolver(new ClasspathResourceURIResolver()); mFactory.setURIResolver(new ClasspathResourceURIResolver());
} }
@@ -104,40 +101,8 @@ public class ZUGFeRDVisualizer {
String ublCreditNoteSignature = "CreditNote"; String ublCreditNoteSignature = "CreditNote";
String cioSignature = "SCRDMCCBDACIOMessageStructure"; String cioSignature = "SCRDMCCBDACIOMessageStructure";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
try
{
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
}
catch (IllegalArgumentException e)
{
LOGGER.warn("Property: \"Access external DTD\" not supported.");
}
try
{
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
}
catch (IllegalArgumentException e)
{
LOGGER.warn("Property: \"Access external schema\" not supported.");
}
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
try { try {
DocumentBuilder db = dbf.newDocumentBuilder(); DocumentBuilder db = XMLTools.getDocumentBuilder(true);
Document doc = db.parse(new InputSource(fis)); Document doc = db.parse(new InputSource(fis));
Element root = doc.getDocumentElement(); Element root = doc.getDocumentElement();
if (root.getLocalName().equals(zf1Signature)) { if (root.getLocalName().equals(zf1Signature)) {

View File

@@ -14,7 +14,6 @@ import java.util.HashMap;
import java.util.Set; import java.util.Set;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathConstants;
@@ -23,6 +22,7 @@ import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.mustangproject.util.ByteArraySearcher; import org.mustangproject.util.ByteArraySearcher;
import org.mustangproject.XMLTools;
import org.mustangproject.ZUGFeRD.ZUGFeRDImporter; import org.mustangproject.ZUGFeRD.ZUGFeRDImporter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -142,12 +142,7 @@ public class PDFValidator extends Validator {
* <zf:Version>1.0</zf:Version> * <zf:Version>1.0</zf:Version>
*/ */
try { try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = XMLTools.getDocumentBuilder(false);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
factory.setXIncludeAware(false);
final DocumentBuilder builder = factory.newDocumentBuilder();
final InputSource is = new InputSource(new StringReader(xmp)); final InputSource is = new InputSource(new StringReader(xmp));
docXMP = builder.parse(is); docXMP = builder.parse(is);

View File

@@ -7,9 +7,8 @@ import java.net.URL;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
import javax.xml.transform.Source; import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.mustangproject.XMLTools;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@@ -59,11 +58,8 @@ public abstract class Validator {
protected void validateSchema(byte[] xmlRawData, String schemaPath, int section, EPart part) throws IrrecoverableValidationError { protected void validateSchema(byte[] xmlRawData, String schemaPath, int section, EPart part) throws IrrecoverableValidationError {
URL schemaFile = Thread.currentThread().getContextClassLoader().getResource("schema/" + schemaPath); URL schemaFile = Thread.currentThread().getContextClassLoader().getResource("schema/" + schemaPath);
Source xmlData = new StreamSource(new ByteArrayInputStream(xmlRawData)); Source xmlData = new StreamSource(new ByteArrayInputStream(xmlRawData));
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try { try {
schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); javax.xml.validation.Validator validator = XMLTools.getValidator(schemaFile);
Schema schema = schemaFactory.newSchema(schemaFile);
javax.xml.validation.Validator validator = schema.newValidator();
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
validator.validate(xmlData); validator.validate(xmlData);
} catch (SAXException e) { } catch (SAXException e) {

View File

@@ -13,9 +13,7 @@ import java.text.ParseException;
import java.util.Calendar; import java.util.Calendar;
import java.util.Set; import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathConstants;
@@ -156,26 +154,7 @@ public class XMLValidator extends Validator {
* *
*/ */
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); final DocumentBuilder db = XMLTools.getDocumentBuilder(true);
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
final DocumentBuilder db = dbf.newDocumentBuilder();
final InputSource is = new InputSource(new StringReader(zfXML)); final InputSource is = new InputSource(new StringReader(zfXML));
final Document doc = db.parse(is); final Document doc = db.parse(is);

View File

@@ -17,9 +17,7 @@ import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
@@ -143,24 +141,7 @@ public class ZUGFeRDValidator {
boolean isXML = false; boolean isXML = false;
String xmlAsString = null; String xmlAsString = null;
try { try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = XMLTools.getDocumentBuilder(true);
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
content = XMLTools.removeBOM(content); content = XMLTools.removeBOM(content);
xmlAsString = new String(content, StandardCharsets.UTF_8); xmlAsString = new String(content, StandardCharsets.UTF_8);