From a89c9ad8443985ee4dd05452b7a589328fa3b157 Mon Sep 17 00:00:00 2001 From: RHEL Packaging Agent Date: Thu, 25 Jun 2026 12:40:54 +0000 Subject: [PATCH] Add XXE protection to XmlStringPrettyFormatter Add secure XML parsing features to prevent XXE attacks by disabling DOCTYPE declarations and external entity processing. Adapted for 3.24.2: keep tests in the old location. --- .../util/xml/XmlStringPrettyFormatter.java | 27 ++++++++++++++- ...ringPrettyFormatter_prettyFormat_Test.java | 33 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/assertj-core/src/main/java/org/assertj/core/util/xml/XmlStringPrettyFormatter.java b/assertj-core/src/main/java/org/assertj/core/util/xml/XmlStringPrettyFormatter.java index 33c035b33..b66b9858b 100644 --- a/assertj-core/src/main/java/org/assertj/core/util/xml/XmlStringPrettyFormatter.java +++ b/assertj-core/src/main/java/org/assertj/core/util/xml/XmlStringPrettyFormatter.java @@ -17,9 +17,13 @@ import static org.assertj.core.util.Preconditions.checkArgument; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; +import java.util.HashMap; +import java.util.Map; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.bootstrap.DOMImplementationRegistry; @@ -39,6 +43,17 @@ public class XmlStringPrettyFormatter { private static final String FORMAT_ERROR = "Unable to format XML string"; + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + private static final Map SECURE_FEATURES = new HashMap<>(); + + static { + SECURE_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING, true); + SECURE_FEATURES.put("http://apache.org/xml/features/disallow-doctype-decl", true); + SECURE_FEATURES.put("http://xml.org/sax/features/external-general-entities", false); + SECURE_FEATURES.put("http://xml.org/sax/features/external-parameter-entities", false); + SECURE_FEATURES.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } + public static String xmlPrettyFormat(String xmlStringToFormat) { checkArgument(xmlStringToFormat != null, "Expecting XML String not to be null"); // convert String to an XML Document and then back to String but prettily formatted. @@ -67,13 +82,23 @@ public class XmlStringPrettyFormatter { private static Document toXmlDocument(String xmlString) { try { InputSource xmlInputSource = new InputSource(new StringReader(xmlString)); - DocumentBuilder xmlDocumentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + DocumentBuilder xmlDocumentBuilder = documentBuilderFactory().newDocumentBuilder(); return xmlDocumentBuilder.parse(xmlInputSource); } catch (Exception e) { throw new RuntimeException(FORMAT_ERROR, e); } } + private static DocumentBuilderFactory documentBuilderFactory() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + SECURE_FEATURES.forEach((name, value) -> { + try { + factory.setFeature(name, value); + } catch (ParserConfigurationException ignore) {} + }); + return factory; + } + private XmlStringPrettyFormatter() { // utility class } diff --git a/assertj-core/src/test/java/org/assertj/core/util/xml/XmlStringPrettyFormatter_prettyFormat_Test.java b/assertj-core/src/test/java/org/assertj/core/util/xml/XmlStringPrettyFormatter_prettyFormat_Test.java index 2c1bf754e..1600e1306 100644 --- a/assertj-core/src/test/java/org/assertj/core/util/xml/XmlStringPrettyFormatter_prettyFormat_Test.java +++ b/assertj-core/src/test/java/org/assertj/core/util/xml/XmlStringPrettyFormatter_prettyFormat_Test.java @@ -97,4 +97,37 @@ class XmlStringPrettyFormatter_prettyFormat_Test { } } + @Test + void should_throw_error_when_xml_string_contains_doctype_with_entity() { + String xmlString = "\n" + + "\n" + + "]>\n" + + "&xxe;"; + try { + xmlPrettyFormat(xmlString); + } catch (Exception e) { + assertThat(e).isInstanceOf(RuntimeException.class).hasMessageStartingWith("Unable to format XML string"); + assertThat(e).hasRootCauseInstanceOf(SAXParseException.class); + assertThat(e.getCause()).hasMessageContaining("DOCTYPE is disallowed"); + } + } + + @Test + void should_throw_error_when_xml_string_contains_doctype_with_parameter_entity() { + String xmlString = "\n" + + "\n" + + " %xxe;\n" + + "]>\n" + + "foo"; + try { + xmlPrettyFormat(xmlString); + } catch (Exception e) { + assertThat(e).isInstanceOf(RuntimeException.class).hasMessageStartingWith("Unable to format XML string"); + assertThat(e).hasRootCauseInstanceOf(SAXParseException.class); + assertThat(e.getCause()).hasMessageContaining("DOCTYPE is disallowed"); + } + } + } -- 2.52.0