diff --git a/core/fr.opensagres.xdocreport.core/src/main/java/fr/opensagres/xdocreport/core/utils/DOMUtils.java b/core/fr.opensagres.xdocreport.core/src/main/java/fr/opensagres/xdocreport/core/utils/DOMUtils.java index ff02fe260..cf71cbd63 100644 --- a/core/fr.opensagres.xdocreport.core/src/main/java/fr/opensagres/xdocreport/core/utils/DOMUtils.java +++ b/core/fr.opensagres.xdocreport.core/src/main/java/fr/opensagres/xdocreport/core/utils/DOMUtils.java @@ -32,9 +32,11 @@ import java.util.Collection; import java.util.Collections; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; @@ -53,10 +55,52 @@ public class DOMUtils { + private static final String DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; + + private static final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + + private static final String EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities"; + + private static final String EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities"; + + /** + * Returns a {@link DocumentBuilderFactory} hardened against XXE: DOCTYPE declarations are + * rejected and external entities/DTDs are not resolved (CVE-2025-65482). + */ + public static DocumentBuilderFactory newSecureDocumentBuilderFactory() + throws ParserConfigurationException + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature( DISALLOW_DOCTYPE_DECL, true ); + factory.setFeature( EXTERNAL_GENERAL_ENTITIES, false ); + factory.setFeature( EXTERNAL_PARAMETER_ENTITIES, false ); + factory.setFeature( LOAD_EXTERNAL_DTD, false ); + factory.setFeature( XMLConstants.FEATURE_SECURE_PROCESSING, true ); + factory.setXIncludeAware( false ); + factory.setExpandEntityReferences( false ); + return factory; + } + + /** + * Returns a {@link SAXParserFactory} hardened against XXE: DOCTYPE declarations are rejected + * and external entities/DTDs are not resolved (CVE-2025-65482). + */ + public static SAXParserFactory newSecureSAXParserFactory() + throws ParserConfigurationException, SAXException + { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setFeature( DISALLOW_DOCTYPE_DECL, true ); + factory.setFeature( EXTERNAL_GENERAL_ENTITIES, false ); + factory.setFeature( EXTERNAL_PARAMETER_ENTITIES, false ); + factory.setFeature( LOAD_EXTERNAL_DTD, false ); + factory.setFeature( XMLConstants.FEATURE_SECURE_PROCESSING, true ); + return factory; + } + public static Document load( InputStream stream ) throws ParserConfigurationException, SAXException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory factory = newSecureDocumentBuilderFactory(); factory.setNamespaceAware( true ); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse( stream ); @@ -65,7 +109,7 @@ public static Document load( InputStream stream ) public static Document load( String xml ) throws ParserConfigurationException, SAXException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory factory = newSecureDocumentBuilderFactory(); factory.setNamespaceAware( true ); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse( IOUtils.toInputStream( xml, EncodingConstants.UTF_8.name() ) ); diff --git a/core/fr.opensagres.xdocreport.core/src/test/java/fr/opensagres/xdocreport/core/utils/DOMUtilsXxeTestCase.java b/core/fr.opensagres.xdocreport.core/src/test/java/fr/opensagres/xdocreport/core/utils/DOMUtilsXxeTestCase.java new file mode 100644 index 000000000..3e5c1ee84 --- /dev/null +++ b/core/fr.opensagres.xdocreport.core/src/test/java/fr/opensagres/xdocreport/core/utils/DOMUtilsXxeTestCase.java @@ -0,0 +1,57 @@ +package fr.opensagres.xdocreport.core.utils; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +import javax.xml.parsers.SAXParser; + +import org.junit.Test; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Regression test for the XXE hardening (incomplete fix of CVE-2025-65482): the secure + * factories returned by {@link DOMUtils} reject a DOCTYPE and do not resolve external entities. + */ +public class DOMUtilsXxeTestCase +{ + + private static final String XXE = + "\n" + + " ]>\n" + + "&x;"; + + @Test + public void loadRejectsDoctype() + throws Exception + { + try + { + DOMUtils.load( new ByteArrayInputStream( XXE.getBytes( StandardCharsets.UTF_8 ) ) ); + fail( "DOMUtils.load must reject a DOCTYPE" ); + } + catch ( SAXException e ) + { + assertTrue( e.getMessage() != null ); + } + } + + @Test + public void secureSAXParserRejectsDoctype() + throws Exception + { + SAXParser parser = DOMUtils.newSecureSAXParserFactory().newSAXParser(); + try + { + parser.parse( new ByteArrayInputStream( XXE.getBytes( StandardCharsets.UTF_8 ) ), new DefaultHandler() ); + fail( "Secure SAXParser must reject a DOCTYPE" ); + } + catch ( SAXException e ) + { + assertTrue( e.getMessage() != null ); + } + } +} diff --git a/document/fr.opensagres.xdocreport.document.docx/src/main/java/fr/opensagres/xdocreport/document/docx/DocxReport.java b/document/fr.opensagres.xdocreport.document.docx/src/main/java/fr/opensagres/xdocreport/document/docx/DocxReport.java index d91fc51e3..e0cef5d84 100644 --- a/document/fr.opensagres.xdocreport.document.docx/src/main/java/fr/opensagres/xdocreport/document/docx/DocxReport.java +++ b/document/fr.opensagres.xdocreport.document.docx/src/main/java/fr/opensagres/xdocreport/document/docx/DocxReport.java @@ -42,7 +42,6 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; @@ -53,6 +52,7 @@ import fr.opensagres.xdocreport.core.io.IEntryReaderProvider; import fr.opensagres.xdocreport.core.io.IEntryWriterProvider; import fr.opensagres.xdocreport.core.io.XDocArchive; +import fr.opensagres.xdocreport.core.utils.DOMUtils; import fr.opensagres.xdocreport.document.AbstractXDocReport; import fr.opensagres.xdocreport.document.docx.images.DocxImageRegistry; import fr.opensagres.xdocreport.document.docx.preprocessor.DefaultStyle; @@ -162,7 +162,7 @@ protected void onBeforePreprocessing( Map sharedContext, XDocArc try { HyperlinkContentHandler contentHandler = new HyperlinkContentHandler(); - SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); + SAXParser saxParser = DOMUtils.newSecureSAXParserFactory().newSAXParser(); saxParser.parse(preprocessedArchive.getEntryInputStream(relsEntryName), contentHandler); if ( contentHandler.getHyperlinks() != null ) { diff --git a/document/fr.opensagres.xdocreport.document/src/main/java/fr/opensagres/xdocreport/document/preprocessor/sax/SAXXDocPreprocessor.java b/document/fr.opensagres.xdocreport.document/src/main/java/fr/opensagres/xdocreport/document/preprocessor/sax/SAXXDocPreprocessor.java index 34ed0aabe..1a24f03ed 100644 --- a/document/fr.opensagres.xdocreport.document/src/main/java/fr/opensagres/xdocreport/document/preprocessor/sax/SAXXDocPreprocessor.java +++ b/document/fr.opensagres.xdocreport.document/src/main/java/fr/opensagres/xdocreport/document/preprocessor/sax/SAXXDocPreprocessor.java @@ -57,6 +57,7 @@ public boolean preprocess( String entryName, InputStream reader, Writer writer, { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); //To avoid xxe security issue + xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false); xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);