Skip to content

XXE: DocumentBuilderFactory and Transformer without secure-processing hardening in core utilities (DOMUtils, XSLTPrettyPrinter, IndentNumberPrettyPrinter) #736

Description

@elarasu

Summary

Several core utility classes parse caller-supplied XML through `DocumentBuilderFactory` or pass it through a `Transformer` over a `StreamSource` without enabling `FEATURE_SECURE_PROCESSING` or disabling DTD / external entity resolution. Default JAXP behavior allows DOCTYPE and external entities, exposing XXE (CWE-611) and billion-laughs-style entity expansion (CWE-776) when callers pass untrusted input.

Discovered via automated SAST sweep (cognium-ai 2.18.3) over top-100 starred Java repos on GitHub. `xdocreport` consumes `.docx`/`.odt`/`.pptx` files (which are zipped XML) and is widely used as a document-template engine; these utilities are exposed throughout the pipeline.

Vulnerable sites

1. `core/fr.opensagres.xdocreport.core/src/main/java/fr/opensagres/xdocreport/core/utils/DOMUtils.java:59, 68`

```java
public static Document load(InputStream stream)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // ← no hardening
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(stream); // ← XXE
}

public static Document load(String xml)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // ← no hardening
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(IOUtils.toInputStream(xml, EncodingConstants.UTF_8.name())); // ← XXE
}
```

These are public, static utility methods called from many places within xdocreport to load XML fragments from `.docx`/`.odt` contents and other sources.

2. `core/fr.opensagres.xdocreport.core/src/main/java/fr/opensagres/xdocreport/core/internal/XSLTPrettyPrinter.java:69`

```java
public String prettyPrint(String xml, int indent) throws Exception {
Transformer transformer = getTemplates(indent).newTransformer();
final StringWriter out = new StringWriter();
transformer.transform(new StreamSource(new StringReader(xml)), // ← XXE: untrusted xml via StreamSource
new StreamResult(out));
return out.toString();
}
```

Although the result is `StreamResult` (output-only), the `StreamSource` over caller-supplied `xml` is read by an XML parser inside the Transformer pipeline — DOCTYPE/entities still get resolved unless secure processing is enabled on the `TransformerFactory` that built the `Templates`.

3. `core/fr.opensagres.xdocreport.core/src/main/java/fr/opensagres/xdocreport/core/internal/IndentNumberPrettyPrinter.java:68`

```java
public String prettyPrint(String xml, int indent) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance(); // ← no hardening
factory.setAttribute(INDENT_NUMBER, indent);
Transformer transformer = factory.newTransformer();
// ...
transformer.transform(new StreamSource(new StringReader(xml)), // ← XXE
new StreamResult(out));
}
```

Same pattern — untrusted XML in via `StreamSource`, no `FEATURE_SECURE_PROCESSING` on the `TransformerFactory`.

`NoIndentNumberPrettyPrinter` (similar class in the same package) and `DocumentBuilderFactory` use in `docx/DocxReport.java:165` and `xslt/DefaultTransformerFactory.java:45` (in the FOP sandbox module) likely have the same shape — recommend reviewing all `DocumentBuilderFactory` / `TransformerFactory` constructions in the codebase.

Impact

  • Out-of-band data exfiltration via external entity references (``) when xdocreport is fed an attacker-crafted .docx/.odt file or XML fragment.
  • Denial of service via billion-laughs / parameter-entity expansion in document processing.
  • Server-side request forgery via external DTD fetches.

The DOMUtils.load methods being public static + namespace-aware suggests they are entry points used wherever xdocreport reads document XML — a malicious .docx/.odt uploaded to any application using xdocreport for templating could trigger this.

Recommended fix

Apply JAXP hardening on all `DocumentBuilderFactory` and `TransformerFactory` instances at construction:

```java
// DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
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.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd\", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);

// TransformerFactory
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
```

A centralized `SafeXmlFactories` helper class in `fr.opensagres.xdocreport.core.utils` would let every call site share one hardened factory.

References:

Discovery attribution

Found by cognium-ai SAST during a top-100 Java repo sweep. Total 9 XXE findings in xdocreport — the 3+ above are confirmed by code read; the remaining sites likely follow the same pattern. Happy to review a patch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions